From 9a32ee694ff3eafa8cf58b5be7c942ef3a093ea8 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 30 Aug 2024 18:23:33 +0400 Subject: [PATCH 01/48] create new page Signed-off-by: Stepan Kiryakov --- frontend/src/app/app-routing.module.ts | 10 + frontend/src/app/app.component.ts | 4 +- frontend/src/app/app.module.ts | 4 + .../policy-statistics.module.ts | 36 + .../policy-statistics.component.html | 103 +++ .../policy-statistics.component.scss | 13 + .../policy-statistics.component.ts | 145 ++++ .../app/services/policy-statistics.service.ts | 28 + frontend/src/app/themes/guardian.scss | 618 ------------------ frontend/src/app/themes/guardian/button.scss | 106 +++ frontend/src/app/themes/guardian/dialog.scss | 105 +++ frontend/src/app/themes/guardian/grid.scss | 242 +++++++ frontend/src/app/themes/guardian/icon.scss | 36 + frontend/src/app/themes/guardian/index.scss | 10 + frontend/src/app/themes/guardian/label.scss | 36 + frontend/src/app/themes/guardian/page.scss | 98 +++ .../src/app/themes/guardian/progress.scss | 129 ++++ .../src/app/themes/guardian/variables.scss | 44 ++ .../src/assets/images/icons/16/left-arrow.svg | 4 + 19 files changed, 1150 insertions(+), 621 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics.module.ts create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts create mode 100644 frontend/src/app/services/policy-statistics.service.ts delete mode 100644 frontend/src/app/themes/guardian.scss create mode 100644 frontend/src/app/themes/guardian/button.scss create mode 100644 frontend/src/app/themes/guardian/dialog.scss create mode 100644 frontend/src/app/themes/guardian/grid.scss create mode 100644 frontend/src/app/themes/guardian/icon.scss create mode 100644 frontend/src/app/themes/guardian/index.scss create mode 100644 frontend/src/app/themes/guardian/label.scss create mode 100644 frontend/src/app/themes/guardian/page.scss create mode 100644 frontend/src/app/themes/guardian/progress.scss create mode 100644 frontend/src/app/themes/guardian/variables.scss create mode 100644 frontend/src/assets/images/icons/16/left-arrow.svg diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 2155848c70..460284e7a0 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -45,6 +45,7 @@ import { UsersManagementComponent } from './views/user-management/user-managemen import { UsersManagementDetailComponent } from './views/user-management-detail/user-management-detail.component'; import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.component'; import { MapService } from './services/map.service'; +import { PolicyStatisticsComponent } from './modules/policy-statistics/policy-statistics/policy-statistics.component'; @Injectable({ providedIn: 'root' @@ -489,6 +490,15 @@ const routes: Routes = [ } }, + { + path: 'policy-statistics', + component: PolicyStatisticsComponent, + canActivate: [PermissionsGuard], + data: { + roles: [UserRole.USER] + } + }, + { path: '', component: HomeComponent }, { path: 'info', component: InfoComponent }, { path: '**', redirectTo: '', pathMatch: 'full' }, diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index b602dae82a..d939eab645 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,7 +1,5 @@ -import { HttpClient } from '@angular/common/http'; import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'; import { AuthStateService } from './services/auth-state.service'; -import { MapService } from './services/map.service'; import { WebSocketService } from './services/web-socket.service'; import { BrandingService } from './services/branding.service'; import './modules/policy-engine/policy-lang-modes/policy-json-lang.mode'; @@ -16,7 +14,7 @@ import { DomSanitizer } from '@angular/platform-browser'; templateUrl: './app.component.html', styleUrls: [ './app.component.scss', - './themes/guardian.scss' + './themes/guardian/index.scss' ], }) export class AppComponent implements OnInit { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index bc3ba2a9a8..33cef81726 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -17,6 +17,7 @@ import { SchemaService } from './services/schema.service'; import { HandleErrorsService } from './services/handle-errors.service'; import { AuditService } from './services/audit.service'; import { PolicyEngineService } from './services/policy-engine.service'; +import { PolicyStatisticsService } from './services/policy-statistics.service'; import { DemoService } from './services/demo.service'; import { PolicyHelper } from './services/policy-helper.service'; import { IPFSService } from './services/ipfs.service'; @@ -72,6 +73,7 @@ import { TagEngineModule } from './modules/tag-engine/tag-engine.module'; import { SchemaEngineModule } from './modules/schema-engine/schema-engine.module' import { ThemeService } from './services/theme.service'; import { RecordService } from './services/record.service'; +import { PolicyStatisticsModule } from './modules/policy-statistics/policy-statistics.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'; @@ -184,6 +186,7 @@ import { WorkerTasksService } from './services/worker-tasks.service'; FormsModule, SchemaEngineModule, PolicyEngineModule, + PolicyStatisticsModule, TagEngineModule, CompareModule, ToastrModule.forRoot(), @@ -225,6 +228,7 @@ import { WorkerTasksService } from './services/worker-tasks.service'; AnalyticsService, AuditService, PolicyEngineService, + PolicyStatisticsService, PolicyHelper, IPFSService, ArtifactService, diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts new file mode 100644 index 0000000000..bff34df7c8 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MaterialModule } from 'src/app/modules/common/material.module'; +import { FormsModule } from '@angular/forms'; +//Modules +import { AppRoutingModule } from 'src/app/app-routing.module'; +import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; +import { CommonComponentsModule } from '../common/common-components.module'; +import { AngularSvgIconModule } from 'angular-svg-icon'; +import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; +import { PolicyStatisticsComponent } from './policy-statistics/policy-statistics.component'; +import { TableModule } from 'primeng/table'; +import { TooltipModule } from 'primeng/tooltip'; + +@NgModule({ + declarations: [ + PolicyStatisticsComponent + ], + imports: [ + CommonModule, + FormsModule, + MaterialModule, + CommonComponentsModule, + SchemaEngineModule, + AppRoutingModule, + DynamicDialogModule, + TableModule, + TooltipModule, + AngularSvgIconModule.forRoot(), + ], + exports: [], + providers: [ + DialogService + ], +}) +export class PolicyStatisticsModule { } diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html new file mode 100644 index 0000000000..1f0c51514e --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html @@ -0,0 +1,103 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+ Policy Name: AM0081 + Version: (1.0.0) +
+
+ +
+ +
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + {{row[column.id]}} + + + + + +
+ +
+
+
+ +
+ + There were no Policies created yet + Please create new policy to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss new file mode 100644 index 0000000000..3a3e9b2cc5 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss @@ -0,0 +1,13 @@ +.policy-name { + color: #848FA9; + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts new file mode 100644 index 0000000000..7d64846399 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts @@ -0,0 +1,145 @@ +import { Component, OnInit } from '@angular/core'; +import { UserPermissions } from '@guardian/interfaces'; +import { forkJoin } from 'rxjs'; +import { PolicyStatisticsService } from 'src/app/services/policy-statistics.service'; +import { ProfileService } from 'src/app/services/profile.service'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-policy-statistics', + templateUrl: './policy-statistics.component.html', + styleUrls: ['./policy-statistics.component.scss'], +}) +export class PolicyStatisticsComponent implements OnInit { + public readonly title: string = 'Statistics'; + + 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[]; + + constructor( + private profileService: ProfileService, + private policyStatisticsService: PolicyStatisticsService + ) { + this.columns = [{ + id: 'name', + title: 'Name', + type: 'text', + size: 'auto', + tooltip: true + }, { + id: 'topic', + title: 'Topic', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'status', + title: 'Status', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'trigger', + title: 'Trigger', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'docs', + title: 'Documents', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: 'auto', + tooltip: false + }] + } + + ngOnInit() { + this.page = []; + this.pageIndex = 0; + this.pageSize = 10; + this.pageCount = 0; + this.loadProfile(); + } + + ngOnDestroy(): void { + + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + forkJoin([ + 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.loading = true; + this.policyStatisticsService + .page(this.pageIndex, this.pageSize) + .subscribe((response) => { + const { page, count } = this.policyStatisticsService.parsePage(response); + this.page = page; + this.pageCount = count; + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + + } + + 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 onCreate() { + + } +} diff --git a/frontend/src/app/services/policy-statistics.service.ts b/frontend/src/app/services/policy-statistics.service.ts new file mode 100644 index 0000000000..6589482e19 --- /dev/null +++ b/frontend/src/app/services/policy-statistics.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { API_BASE_URL } from './api'; + +/** + * Services for working from statistics and separate blocks. + */ +@Injectable() +export class PolicyStatisticsService { + private readonly url: string = `${API_BASE_URL}/policies`; + + constructor(private http: HttpClient) { + } + + public page(pageIndex?: number, pageSize?: number): Observable> { + if (Number.isInteger(pageIndex) && Number.isInteger(pageSize)) { + return this.http.get(`${this.url}?pageIndex=${pageIndex}&pageSize=${pageSize}`, { observe: 'response' }); + } + return this.http.get(`${this.url}`, { observe: 'response' }); + } + + public parsePage(response: HttpResponse) { + const page = response.body || []; + const count = Number(response.headers.get('X-Total-Count')) || page.length; + return { page, count }; + } +} diff --git a/frontend/src/app/themes/guardian.scss b/frontend/src/app/themes/guardian.scss deleted file mode 100644 index 506828b6a0..0000000000 --- a/frontend/src/app/themes/guardian.scss +++ /dev/null @@ -1,618 +0,0 @@ -::ng-deep body { - --guardian-font-family: Inter, serif; - --guardian-font-style: normal; - --guardian-header-color: #000; - --guardian-font-color: #23252E; - - --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-disabled-color: #848FA9; - --guardian-disabled-background: #EFF3F7; - - --guardian-dry-run-color: #4169E2; - --guardian-dry-run-background: #f5f7fd; - - --guardian-grey-color: #EFF3F7; - --guardian-grey-background: #F9FAFC; - --guardian-grey-color-2: #E1E7EF; - - --guardian-delete-color: #FF432A; - --guardian-delete-background: #FFEAEA; - - --guardian-grid-color: #848FA9; - --guardian-border-color: #E1E7EF; - - --guardian-close-color: #848FA9; - - --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; - - .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-color); - color: var(--guardian-disabled-color); - } - - &-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); - } - - &-close { - fill: var(--guardian-close-color); - color: var(--guardian-close-color); - } - } - - .guardian-loading { - background: var(--guardian-secondary-background); - position: absolute; - z-index: 99; - border-radius: 16px; - 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; - } - } - } - - .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; - - &: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; - } - - .guardian-button-icon+.guardian-button-label { - padding-left: 8px; - } - - .guardian-button-label+.guardian-button-icon { - margin-right: 8px; - } - } - - 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-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); - } - } - - .guardian-icon-button { - display: flex; - border-radius: 6px; - 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 5px var(--guardian-primary-background); - } - } - - 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; - } - - .guardian-dialog { - .p-dialog-content { - border-top-left-radius: 16px; - border-top-right-radius: 16px; - border-bottom-left-radius: 16px; - border-bottom-right-radius: 16px; - padding: 0 32px 32px 32px; - } - - .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; - } - - .dialog-button, - .cancel-button { - margin-left: auto; - margin-right: 0px; - - button { - width: 100px; - height: 40px; - font-size: var(--guardian-primary-font-size); - font-weight: 500; - } - } - - .ok-button { - margin-left: 15px; - - button { - width: auto; - height: 40px; - font-size: var(--guardian-primary-font-size); - font-weight: 500; - } - } - } - } - - .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); - } - } - } - - .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: 12px 6px !important; - } - - .col-auto { - width: 100%; - } - - .col-44 { - width: 44px; - min-width: 44px; - max-width: 44px; - } - - .col-64 { - width: 64px; - min-width: 64px; - max-width: 64px; - } - - .col-80 { - width: 80px; - min-width: 80px; - max-width: 80px; - } - - .col-100 { - width: 100px; - min-width: 100px; - max-width: 100px; - } - - .col-120 { - width: 120px; - min-width: 120px; - max-width: 120px; - } - - .col-135 { - width: 135px; - min-width: 135px; - max-width: 135px; - } - - .col-140 { - width: 140px; - min-width: 140px; - max-width: 140px; - } - - .col-150 { - width: 150px; - min-width: 150px; - max-width: 150px; - } - - .col-160 { - width: 160px; - min-width: 160px; - max-width: 160px; - } - - .col-180 { - width: 180px; - min-width: 180px; - max-width: 180px; - } - - .col-200 { - width: 200px; - min-width: 200px; - max-width: 200px; - } - - .col-250 { - width: 250px; - min-width: 250px; - max-width: 250px; - } - - .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; - - .dialog-grid-row { - display: flex; - flex-direction: row; - width: 100%; - height: 64px; - min-height: 64px; - max-height: 64px; - 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); - - &>div { - padding: 22px 10px; - display: flex; - align-items: center; - } - - &[expand="true"] { - border-bottom-color: transparent; - - .expand-icon { - transform: rotate(90deg); - } - } - } - - .dialog-grid-expand-row { - width: 100%; - border-bottom: 1px solid var(--guardian-border-color); - padding: 0px 12px; - display: none; - - &[expand="true"] { - display: flex; - } - } - } - } -} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/button.scss b/frontend/src/app/themes/guardian/button.scss new file mode 100644 index 0000000000..52f67d108e --- /dev/null +++ b/frontend/src/app/themes/guardian/button.scss @@ -0,0 +1,106 @@ +.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; + + &: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; + } + + .guardian-button-icon+.guardian-button-label { + padding-left: 8px; + } + + .guardian-button-label+.guardian-button-icon { + margin-right: 8px; + } +} + +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: 6px; + 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 5px var(--guardian-primary-background); + } +} + +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; +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/dialog.scss b/frontend/src/app/themes/guardian/dialog.scss new file mode 100644 index 0000000000..ee7bb87cd3 --- /dev/null +++ b/frontend/src/app/themes/guardian/dialog.scss @@ -0,0 +1,105 @@ +.guardian-dialog { + .p-dialog-content { + border-top-left-radius: 16px; + border-top-right-radius: 16px; + border-bottom-left-radius: 16px; + border-bottom-right-radius: 16px; + padding: 0 32px 32px 32px; + } + + .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; + } + + .dialog-button, + .cancel-button { + margin-left: auto; + margin-right: 0px; + + button { + width: 100px; + height: 40px; + font-size: var(--guardian-primary-font-size); + font-weight: 500; + } + } + + .ok-button { + margin-left: 15px; + + button { + width: auto; + height: 40px; + font-size: var(--guardian-primary-font-size); + font-weight: 500; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/grid.scss b/frontend/src/app/themes/guardian/grid.scss new file mode 100644 index 0000000000..4b5ec44b6f --- /dev/null +++ b/frontend/src/app/themes/guardian/grid.scss @@ -0,0 +1,242 @@ +.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: 12px 6px !important; + } + + .col-auto { + width: 100%; + } + + .col-44 { + width: 44px; + min-width: 44px; + max-width: 44px; + } + + .col-64 { + width: 64px; + min-width: 64px; + max-width: 64px; + } + + .col-80 { + width: 80px; + min-width: 80px; + max-width: 80px; + } + + .col-100 { + width: 100px; + min-width: 100px; + max-width: 100px; + } + + .col-120 { + width: 120px; + min-width: 120px; + max-width: 120px; + } + + .col-135 { + width: 135px; + min-width: 135px; + max-width: 135px; + } + + .col-140 { + width: 140px; + min-width: 140px; + max-width: 140px; + } + + .col-150 { + width: 150px; + min-width: 150px; + max-width: 150px; + } + + .col-160 { + width: 160px; + min-width: 160px; + max-width: 160px; + } + + .col-180 { + width: 180px; + min-width: 180px; + max-width: 180px; + } + + .col-200 { + width: 200px; + min-width: 200px; + max-width: 200px; + } + + .col-250 { + width: 250px; + min-width: 250px; + max-width: 250px; + } + + .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; + + .dialog-grid-row { + display: flex; + flex-direction: row; + width: 100%; + height: 64px; + min-height: 64px; + max-height: 64px; + 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); + + &>div { + padding: 22px 10px; + display: flex; + align-items: center; + } + + &[expand="true"] { + border-bottom-color: transparent; + + .expand-icon { + transform: rotate(90deg); + } + } + } + + .dialog-grid-expand-row { + width: 100%; + border-bottom: 1px solid var(--guardian-border-color); + padding: 0px 12px; + display: none; + + &[expand="true"] { + display: flex; + } + } + } +} + +.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: Inter, sans-serif; + 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; + } +} + +.guardian-grid-container { + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + left: -1px; + right: -1px; + top: 45px; + bottom: -1px; + pointer-events: none; + border: 1px solid #00000014; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px #00000014; + } + + .guardian-grid-header { + height: 46px; + + th { + 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; + + &:first-child { + padding-left: 24px; + } + &:last-child { + padding-left: 24px; + } + } + } + + .guardian-grid-row { + height: 64px; + + td { + padding: 16px; + border-bottom: 1px solid #e9ecef; + + &:first-child { + padding-left: 24px; + } + &:last-child { + padding-left: 24px; + } + } + + &:first-child { + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/icon.scss b/frontend/src/app/themes/guardian/icon.scss new file mode 100644 index 0000000000..9c9ece546f --- /dev/null +++ b/frontend/src/app/themes/guardian/icon.scss @@ -0,0 +1,36 @@ +.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-color); + color: var(--guardian-disabled-color); + } + + &-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); + } + + &-close { + fill: var(--guardian-close-color); + color: var(--guardian-close-color); + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss new file mode 100644 index 0000000000..b6a93f51c6 --- /dev/null +++ b/frontend/src/app/themes/guardian/index.scss @@ -0,0 +1,10 @@ +::ng-deep body { + @import 'variables.scss'; + @import 'icon.scss'; + @import 'button.scss'; + @import 'grid.scss'; + @import 'dialog.scss'; + @import 'progress.scss'; + @import 'label.scss'; + @import 'page.scss'; +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/label.scss b/frontend/src/app/themes/guardian/label.scss new file mode 100644 index 0000000000..19ee41dce7 --- /dev/null +++ b/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/frontend/src/app/themes/guardian/page.scss b/frontend/src/app/themes/guardian/page.scss new file mode 100644 index 0000000000..0b1a6ee153 --- /dev/null +++ b/frontend/src/app/themes/guardian/page.scss @@ -0,0 +1,98 @@ +.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: 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; + + &__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; + } + + &__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; + } + } + + .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: 26px 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-grid { + height: 100%; + width: 100%; + position: relative; + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/progress.scss b/frontend/src/app/themes/guardian/progress.scss new file mode 100644 index 0000000000..a27710f625 --- /dev/null +++ b/frontend/src/app/themes/guardian/progress.scss @@ -0,0 +1,129 @@ +.guardian-loading { + background: var(--guardian-secondary-background); + position: absolute; + z-index: 99; + border-radius: 16px; + 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/frontend/src/app/themes/guardian/variables.scss b/frontend/src/app/themes/guardian/variables.scss new file mode 100644 index 0000000000..9ec8f365be --- /dev/null +++ b/frontend/src/app/themes/guardian/variables.scss @@ -0,0 +1,44 @@ +& { + --guardian-font-family: Inter, serif; + --guardian-font-style: normal; + --guardian-header-color: #000; + --guardian-font-color: #23252E; + + --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-disabled-color: #848FA9; + --guardian-disabled-background: #EFF3F7; + + --guardian-dry-run-color: #4169E2; + --guardian-dry-run-background: #f5f7fd; + + --guardian-grey-color: #EFF3F7; + --guardian-grey-background: #F9FAFC; + --guardian-grey-color-2: #E1E7EF; + + --guardian-delete-color: #FF432A; + --guardian-delete-background: #FFEAEA; + + --guardian-grid-color: #848FA9; + --guardian-border-color: #E1E7EF; + + --guardian-close-color: #848FA9; + + --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; +} \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/left-arrow.svg b/frontend/src/assets/images/icons/16/left-arrow.svg new file mode 100644 index 0000000000..eb43913217 --- /dev/null +++ b/frontend/src/assets/images/icons/16/left-arrow.svg @@ -0,0 +1,4 @@ + + + From 4a958ae4997a8b3a6266c03c49a565b7853726db Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 2 Sep 2024 17:42:52 +0400 Subject: [PATCH 02/48] update Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 112 ++++++++++++++++++ api-gateway/src/app.module.ts | 2 + api-gateway/src/helpers/guardians.ts | 34 +++++- .../middlewares/validation/schemas/index.ts | 1 + .../validation/schemas/statistics.dto.ts | 32 +++++ ...ew-policy-statistics-dialog.component.html | 74 ++++++++++++ ...ew-policy-statistics-dialog.component.scss | 50 ++++++++ .../new-policy-statistics-dialog.component.ts | 55 +++++++++ .../policy-statistics.module.ts | 9 +- .../policy-statistics.component.html | 61 +++++++--- .../policy-statistics.component.scss | 7 +- .../policy-statistics.component.ts | 77 +++++++++++- .../app/services/policy-statistics.service.ts | 6 +- .../src/app/themes/guardian/dropdown.scss | 59 +++++++++ frontend/src/app/themes/guardian/index.scss | 2 + frontend/src/app/themes/guardian/input.scss | 35 ++++++ frontend/src/app/themes/guardian/page.scss | 14 +++ .../src/app/utils/permissions-interface.ts | 1 + .../src/app/views/new-header/menu.model.ts | 6 + .../src/api/statistics.service.ts | 71 +++++++++++ guardian-service/src/app.ts | 2 + interfaces/src/helpers/permissions-helper.ts | 9 ++ .../src/type/messages/message-api.type.ts | 2 + interfaces/src/type/permissions.type.ts | 28 ++++- 24 files changed, 715 insertions(+), 34 deletions(-) create mode 100644 api-gateway/src/api/service/policy-statistics.ts create mode 100644 api-gateway/src/middlewares/validation/schemas/statistics.dto.ts create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts create mode 100644 frontend/src/app/themes/guardian/dropdown.scss create mode 100644 frontend/src/app/themes/guardian/input.scss create mode 100644 guardian-service/src/api/statistics.service.ts diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts new file mode 100644 index 0000000000..8e85119018 --- /dev/null +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -0,0 +1,112 @@ +import { IAuthUser, PinoLogger } from '@guardian/common'; +import { Body, Controller, Get, HttpCode, HttpException, HttpStatus, Post, Query, Req, Response } from '@nestjs/common'; +import { Permissions } from '@guardian/interfaces'; +import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels } from '@nestjs/swagger'; +import { InternalServerErrorDTO, StatisticsDTO, pageHeader } from '#middlewares'; +import { UseCache, Guardians, InternalException, ONLY_SR, EntityOwner, CacheService } from '#helpers'; +import { AuthUser, Auth } from '#auth'; + +@Controller('policy-statistics') +@ApiTags('policy-statistics') +export class PolicyStatisticsApi { + constructor( + private readonly cacheService: CacheService, + private readonly logger: PinoLogger + ) { + } + + /** + * Creates a new statistics + */ + @Post('/') + @Auth(Permissions.STATISTICS_STATISTIC_CREATE) + @ApiOperation({ + summary: 'Creates a new statistics.', + description: 'Creates a new statistics.' + ONLY_SR, + }) + @ApiBody({ + description: 'Configuration.', + type: StatisticsDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: StatisticsDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async createNewStatistic( + @AuthUser() user: IAuthUser, + @Body() newItem: StatisticsDTO + ): Promise { + try { + if (!newItem) { + throw new HttpException('Invalid statistics config', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.createStatistics(newItem, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Return a list of all statistics.', + description: 'Returns all statistics.' + ONLY_SR, + }) + @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: StatisticsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + @UseCache() + async getStatistics( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getStatistics({ + pageIndex, + pageSize + }, owner); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } +} diff --git a/api-gateway/src/app.module.ts b/api-gateway/src/app.module.ts index ad812e3061..adeafe592e 100644 --- a/api-gateway/src/app.module.ts +++ b/api-gateway/src/app.module.ts @@ -40,6 +40,7 @@ import { cacheProvider } from './helpers/providers/cache-provider.js'; import { CacheService } from './helpers/cache-service.js'; import { PermissionsApi } from './api/service/permissions.js'; import { WorkerTasksController } from './api/service/worker-tasks.js'; +import { PolicyStatisticsApi } from './api/service/policy-statistics.js'; import { loggerMongoProvider, pinoLoggerProvider } from './helpers/providers/index.js'; // const JSON_REQUEST_LIMIT = process.env.JSON_REQUEST_LIMIT || '1mb'; @@ -91,6 +92,7 @@ import { loggerMongoProvider, pinoLoggerProvider } from './helpers/providers/ind RecordApi, AISuggestionsAPI, PermissionsApi, + PolicyStatisticsApi, WorkerTasksController ], providers: [ diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 5485a2b9f9..817f41c0ba 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -29,7 +29,7 @@ import { } from '@guardian/interfaces'; import { IAuthUser, NatsService } from '@guardian/common'; import { NewTask } from './task-manager.js'; -import { ModuleDTO, TagDTO, ThemeDTO, TokenDTO, ToolDTO } from '#middlewares'; +import { ModuleDTO, TagDTO, ThemeDTO, TokenDTO, ToolDTO, StatisticsDTO } from '#middlewares'; /** * Filters type @@ -527,7 +527,7 @@ export class Guardians extends NatsService { * @returns {any} Demo Key */ public async generateDemoKey(role: string, userId: string): Promise { - return await this.sendMessage(MessageAPI.GENERATE_DEMO_KEY, {role, userId}); + return await this.sendMessage(MessageAPI.GENERATE_DEMO_KEY, { role, userId }); } /** @@ -537,7 +537,7 @@ export class Guardians extends NatsService { * @param userId */ public async generateDemoKeyAsync(role: string, task: NewTask, userId: string): Promise { - return await this.sendMessage(MessageAPI.GENERATE_DEMO_KEY_ASYNC, {role, task, userId}); + return await this.sendMessage(MessageAPI.GENERATE_DEMO_KEY_ASYNC, { role, task, userId }); } /** @@ -2818,7 +2818,7 @@ export class Guardians extends NatsService { * @param pageSize */ public async getAllWorkerTasks(user: IAuthUser, pageIndex: number, pageSize: number): Promise { - return this.sendMessage(QueueEvents.GET_TASKS_BY_USER, {userId: user.id.toString(), pageIndex, pageSize}); + return this.sendMessage(QueueEvents.GET_TASKS_BY_USER, { userId: user.id.toString(), pageIndex, pageSize }); } /** @@ -2827,7 +2827,7 @@ export class Guardians extends NatsService { * @param userId */ public async restartTask(taskId: string, userId: string) { - return this.sendMessage(QueueEvents.RESTART_TASK, {taskId, userId}); + return this.sendMessage(QueueEvents.RESTART_TASK, { taskId, userId }); } /** @@ -2836,6 +2836,28 @@ export class Guardians extends NatsService { * @param userId */ public async deleteTask(taskId: string, userId: string) { - return this.sendMessage(QueueEvents.DELETE_TASK, {taskId, userId}); + return this.sendMessage(QueueEvents.DELETE_TASK, { taskId, userId }); } + + /** + * Create statistic + * @param statistic + * @param owner + * @returns statistic + */ + public async createStatistics(statistic: StatisticsDTO, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.CREATE_STATISTIC, { statistic, owner }); + } + + /** + * Return tools + * + * @param {IFilter} [params] + * + * @returns {ResponseAndCount} + */ + public async getStatistics(filters: IFilter, owner: IOwner): Promise> { + return await this.sendMessage(MessageAPI.GET_STATISTICS, { filters, owner }); + } + } diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index ee975ff784..683715084d 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -30,3 +30,4 @@ export * from './schemas.dto.js' export * from './policies.dto.js' export * from './profiles.dto.js' export * from './worker-tasks.dto.js' +export * from './statistics.dto.js' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts b/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts new file mode 100644 index 0000000000..b2e988206a --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Examples } from '../examples.js'; + +export class StatisticsDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.UUID + }) + uuid?: string; + + @ApiProperty({ + type: 'string', + required: true, + example: 'Tool name' + }) + name: string; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Description' + }) + description?: string; +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html new file mode 100644 index 0000000000..ac266c0268 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html @@ -0,0 +1,74 @@ +
+
+
Create New
+
{{policy.name}}
+
+
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+ +
+ + + +
+ Policy + {{ currentPolicy.name }} +
+
+ + +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ +
+
+
+
+
+ +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss new file mode 100644 index 0000000000..04bdce407c --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss @@ -0,0 +1,50 @@ +.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 { + margin-bottom: 24px; +} + +.dialog-body { + height: 300px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + + &>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/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts new file mode 100644 index 0000000000..103606962c --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'new-policy-statistics-dialog', + templateUrl: './new-policy-statistics-dialog.component.html', + styleUrls: ['./new-policy-statistics-dialog.component.scss'], +}) +export class NewPolicyStatisticsDialog { + 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) + }); + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.policies = this.config.data?.policies || []; + this.policies = this.policies.filter((p) => p.instanceTopicId); + this.policy = this.config.data?.policy || null; + 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) { + this.ref.close(this.dataForm.value); + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index bff34df7c8..f80d4e1d7f 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MaterialModule } from 'src/app/modules/common/material.module'; import { FormsModule } from '@angular/forms'; -//Modules +import { InputTextModule } from 'primeng/inputtext'; import { AppRoutingModule } from 'src/app/app-routing.module'; import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; import { CommonComponentsModule } from '../common/common-components.module'; @@ -11,10 +11,13 @@ import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; import { PolicyStatisticsComponent } from './policy-statistics/policy-statistics.component'; import { TableModule } from 'primeng/table'; import { TooltipModule } from 'primeng/tooltip'; +import { DropdownModule } from 'primeng/dropdown'; +import { NewPolicyStatisticsDialog } from './dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; @NgModule({ declarations: [ - PolicyStatisticsComponent + PolicyStatisticsComponent, + NewPolicyStatisticsDialog ], imports: [ CommonModule, @@ -26,6 +29,8 @@ import { TooltipModule } from 'primeng/tooltip'; DynamicDialogModule, TableModule, TooltipModule, + InputTextModule, + DropdownModule, AngularSvgIconModule.forRoot(), ], exports: [], diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html index 1f0c51514e..0e20fd2ebf 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html @@ -24,24 +24,55 @@
{{title}} -
- Policy Name: AM0081 - Version: (1.0.0) -
+
- +
+ + +
+ Policy + {{ currentPolicy.name }} +
+
+ Policy + All +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ All +
+
+
+
+
+ +
diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss index 3a3e9b2cc5..007c9d9fcd 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.scss @@ -10,4 +10,9 @@ .policy-version { padding-left: 16px; } -} \ No newline at end of file +} +.guardian-dropdown { + &::ng-deep .p-dropdown { + width: 250px; + } +} diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts index 7d64846399..1a0433ffdd 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts @@ -1,8 +1,12 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { UserPermissions } from '@guardian/interfaces'; -import { forkJoin } from 'rxjs'; +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'; +import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; interface IColumn { id: string; @@ -31,10 +35,18 @@ export class PolicyStatisticsComponent implements OnInit { 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 policyStatisticsService: PolicyStatisticsService + private policyStatisticsService: PolicyStatisticsService, + private policyEngineService: PolicyEngineService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute ) { this.columns = [{ id: 'name', @@ -80,22 +92,40 @@ export class PolicyStatisticsComponent implements OnInit { this.pageIndex = 0; this.pageSize = 10; this.pageCount = 0; - this.loadProfile(); + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + // this.loadProfile(); } ngOnDestroy(): void { - + this.subscription.unsubscribe(); } private loadProfile() { + // const policyId = this.route.snapshot.params['policyId']; this.isConfirmed = false; this.loading = true; forkJoin([ this.profileService.getProfile(), - ]).subscribe(([profile]) => { + 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', + instanceTopicId: null + }); + + const topic = this.route.snapshot.queryParams['topic']; + this.currentPolicy = + this.allPolicies.find((p) => p.instanceTopicId === topic) || + this.allPolicies[0]; + if (this.isConfirmed) { this.loadData(); } else { @@ -125,7 +155,7 @@ export class PolicyStatisticsComponent implements OnInit { } public onBack() { - + this.router.navigate(['/policy-viewer']); } public onPage(event: any): void { @@ -139,7 +169,42 @@ export class PolicyStatisticsComponent implements OnInit { this.loadData(); } + public onFilter(event: any) { + if (event.value === null) { + this.currentPolicy = this.allPolicies[0]; + } + this.pageIndex = 0; + const topic = this.currentPolicy?.instanceTopicId || 'all' + this.router.navigate(['/policy-statistics'], { queryParams: { topic } }); + this.loadData(); + } + public onCreate() { + const dialogRef = this.dialogService.open(NewPolicyStatisticsDialog, { + showHeader: false, + header: 'Create New', + width: '640px', + styleClass: 'guardian-dialog', + data: { + policies: this.allPolicies, + policy: this.currentPolicy, + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + this.create(result) + } + }); + } + private create(item: any) { + this.loading = true; + this.policyStatisticsService + .create(item) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); } } diff --git a/frontend/src/app/services/policy-statistics.service.ts b/frontend/src/app/services/policy-statistics.service.ts index 6589482e19..896d08a05f 100644 --- a/frontend/src/app/services/policy-statistics.service.ts +++ b/frontend/src/app/services/policy-statistics.service.ts @@ -8,7 +8,7 @@ import { API_BASE_URL } from './api'; */ @Injectable() export class PolicyStatisticsService { - private readonly url: string = `${API_BASE_URL}/policies`; + private readonly url: string = `${API_BASE_URL}/policy-statistics`; constructor(private http: HttpClient) { } @@ -25,4 +25,8 @@ export class PolicyStatisticsService { const count = Number(response.headers.get('X-Total-Count')) || page.length; return { page, count }; } + + public create(policy: any): Observable { + return this.http.post(`${this.url}/`, policy); + } } diff --git a/frontend/src/app/themes/guardian/dropdown.scss b/frontend/src/app/themes/guardian/dropdown.scss new file mode 100644 index 0000000000..acc9142634 --- /dev/null +++ b/frontend/src/app/themes/guardian/dropdown.scss @@ -0,0 +1,59 @@ +.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; + + &: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); + } + } + } + + .guardian-dropdown-selected { + text-overflow: ellipsis; + display: block; + white-space: nowrap; + overflow: hidden; + + .guardian-dropdown-label { + 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: Inter; + font-size: 14px; + line-height: 16px; + text-align: left; + } + } + + .guardian-dropdown-item { + + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss index b6a93f51c6..02f01e7c37 100644 --- a/frontend/src/app/themes/guardian/index.scss +++ b/frontend/src/app/themes/guardian/index.scss @@ -7,4 +7,6 @@ @import 'progress.scss'; @import 'label.scss'; @import 'page.scss'; + @import 'input.scss'; + @import 'dropdown.scss'; } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/input.scss b/frontend/src/app/themes/guardian/input.scss new file mode 100644 index 0000000000..9b4d6f1dca --- /dev/null +++ b/frontend/src/app/themes/guardian/input.scss @@ -0,0 +1,35 @@ +.guardian-input-container { + display: flex; + flex-direction: column; + height: 60px; + width: 100%; + + label { + 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); + + &: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%; + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/page.scss b/frontend/src/app/themes/guardian/page.scss index 0b1a6ee153..fddc92dd5e 100644 --- a/frontend/src/app/themes/guardian/page.scss +++ b/frontend/src/app/themes/guardian/page.scss @@ -88,6 +88,20 @@ 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; + } } .guardian-user-page-grid { diff --git a/frontend/src/app/utils/permissions-interface.ts b/frontend/src/app/utils/permissions-interface.ts index fe4d3d4e4f..213aab9dc1 100644 --- a/frontend/src/app/utils/permissions-interface.ts +++ b/frontend/src/app/utils/permissions-interface.ts @@ -164,6 +164,7 @@ export const categoryNames = new Map([ [PermissionCategories.AUDIT, 'Audit'], [PermissionCategories.TOOLS, 'Tools'], [PermissionCategories.PERMISSIONS, 'Permissions'], + [PermissionCategories.STATISTICS, 'Policy Statistics'], [PermissionCategories.ACCESS, 'Access'] ]) diff --git a/frontend/src/app/views/new-header/menu.model.ts b/frontend/src/app/views/new-header/menu.model.ts index 0964e9a38b..1fa8f6f0d7 100644 --- a/frontend/src/app/views/new-header/menu.model.ts +++ b/frontend/src/app/views/new-header/menu.model.ts @@ -143,6 +143,12 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { routerLink: '/policy-viewer' }); } + if (user.STATISTICS_STATISTIC_READ) { + childItems.push({ + title: 'Statistics', + routerLink: '/policy-statistics' + }); + } } if ( user.SCHEMAS_SCHEMA_READ || diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts new file mode 100644 index 0000000000..7d74326228 --- /dev/null +++ b/guardian-service/src/api/statistics.service.ts @@ -0,0 +1,71 @@ +import { ApiResponse } from './helpers/api-response.js'; +import { DatabaseServer, MessageError, MessageResponse, PinoLogger } from '@guardian/common'; +import { IOwner, MessageAPI } from '@guardian/interfaces'; + +/** + * Connect to the message broker methods of working with statistics. + */ +export async function statisticsAPI(logger: PinoLogger): Promise { + /** + * Create new statistic + * + * @param payload - statistic + * + * @returns {any} new statistic + */ + ApiResponse(MessageAPI.CREATE_STATISTIC, + async (msg: { statistic: any, owner: IOwner }) => { + try { + if (!msg) { + throw new Error('Invalid Params'); + } + const { statistic, owner } = msg; + + + return new MessageResponse({}); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + ApiResponse(MessageAPI.GET_STATISTICS, + async (msg: { filters: any, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid load tools parameter'); + } + const { filters, owner } = msg; + const { pageIndex, pageSize } = filters; + + 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.fields = [ + 'id', + 'creator', + 'owner', + 'name', + 'description' + ]; + // const [items, count] = await DatabaseServer.getToolsAndCount( + // { + // owner: owner.owner + // }, + // otherOptions + // ); + return new MessageResponse({ items: [{}], count: 1 }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); +} diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index a1039a620d..bc840ed663 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -25,6 +25,7 @@ import { PolicyServiceChannelsContainer } from './helpers/policy-service-channel import { PolicyEngine } from './policy-engine/policy-engine.js'; import { modulesAPI } from './api/module.service.js'; import { toolsAPI } from './api/tool.service.js'; +import { statisticsAPI } from './api/statistics.service.js'; import { GuardiansService } from './helpers/guardians.js'; import { mapAPI } from './api/map.service.js'; import { tagsAPI } from './api/tag.service.js'; @@ -215,6 +216,7 @@ Promise.all([ await projectsAPI(logger); await AssignedEntityAPI(logger) await permissionAPI(logger); + await statisticsAPI(logger); } catch (error) { console.error(error.message); process.exit(0); diff --git a/interfaces/src/helpers/permissions-helper.ts b/interfaces/src/helpers/permissions-helper.ts index 119855f198..118682f651 100644 --- a/interfaces/src/helpers/permissions-helper.ts +++ b/interfaces/src/helpers/permissions-helper.ts @@ -498,6 +498,15 @@ export class UserPermissions { return this.check(Permissions.DELEGATION_ROLE_MANAGE); } + //STATISTICS + public get STATISTICS_STATISTIC_CREATE(): boolean { + return this.check(Permissions.STATISTICS_STATISTIC_CREATE); + } + + public get STATISTICS_STATISTIC_READ(): boolean { + return this.check(Permissions.STATISTICS_STATISTIC_READ); + } + public static isPolicyAdmin(user: any): boolean { return ( UserPermissions.has(user, Permissions.POLICIES_MIGRATION_CREATE) || diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 3cf7a01b33..6fd54678cb 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -217,6 +217,8 @@ export enum MessageAPI { DELETE_ROLE = 'DELETE_ROLE_VC', SET_ROLE = 'SET_ROLE_VC', CHECK_KEY_PERMISSIONS = 'CHECK_KEY_PERMISSIONS', + CREATE_STATISTIC = 'CREATE_STATISTIC', + GET_STATISTICS = 'GET_STATISTICS', } /** diff --git a/interfaces/src/type/permissions.type.ts b/interfaces/src/type/permissions.type.ts index 385f991c1f..1c16eb2eaf 100644 --- a/interfaces/src/type/permissions.type.ts +++ b/interfaces/src/type/permissions.type.ts @@ -23,7 +23,8 @@ export enum PermissionCategories { TOOLS = 'TOOLS', PERMISSIONS = 'PERMISSIONS', ACCESS = 'ACCESS', - DELEGATION = 'DELEGATION' + DELEGATION = 'DELEGATION', + STATISTICS = 'STATISTICS', } /** @@ -62,7 +63,8 @@ export enum PermissionEntities { THEME = 'THEME', TOKEN = 'TOKEN', TRUST_CHAIN = 'TRUST_CHAIN', - ROLE = 'ROLE' + ROLE = 'ROLE', + STATISTIC = 'STATISTIC' } /** @@ -215,6 +217,9 @@ export enum Permissions { ACCESS_POLICY_ASSIGNED_AND_PUBLISHED = 'ACCESS_POLICY_ASSIGNED_AND_PUBLISHED', //DELEGATION DELEGATION_ROLE_MANAGE = 'DELEGATION_ROLE_MANAGE', + //STATISTICS + STATISTICS_STATISTIC_CREATE = 'STATISTICS_STATISTIC_CREATE', + STATISTICS_STATISTIC_READ = 'STATISTICS_STATISTIC_READ' } /** @@ -1120,6 +1125,21 @@ export const PermissionsArray: { Permissions.PERMISSIONS_ROLE_READ ] }, + //STATISTIC + { + name: Permissions.STATISTICS_STATISTIC_READ, + category: PermissionCategories.STATISTICS, + entity: PermissionEntities.STATISTIC, + action: PermissionActions.READ, + disabled: false + }, + { + name: Permissions.STATISTICS_STATISTIC_CREATE, + category: PermissionCategories.STATISTICS, + entity: PermissionEntities.STATISTIC, + action: PermissionActions.CREATE, + disabled: false + }, //ACCESS { name: Permissions.ACCESS_POLICY_ALL, @@ -1281,7 +1301,9 @@ export const DefaultRoles: Permissions[] = [ Permissions.TOKENS_TOKEN_EXECUTE, Permissions.TAGS_TAG_READ, Permissions.TAGS_TAG_CREATE, - Permissions.ACCESS_POLICY_ASSIGNED_AND_PUBLISHED + Permissions.ACCESS_POLICY_ASSIGNED_AND_PUBLISHED, + Permissions.STATISTICS_STATISTIC_READ, + Permissions.STATISTICS_STATISTIC_CREATE ]; export const OldRoles: Permissions[] = [ From 9be95d6b83dcd3593c24eb563df15b3d214c4ab5 Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Mon, 2 Sep 2024 13:45:42 +0000 Subject: [PATCH 03/48] [skip ci] Add swagger.yaml --- swagger.yaml | 193 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 150 insertions(+), 43 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 1d18f0fd00..78181f7f14 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -11978,6 +11978,92 @@ paths: tags: *ref_26 security: - bearer: [] + /policy-statistics: + post: + operationId: PolicyStatisticsApi_createNewStatistic + summary: Creates a new statistics. + description: >- + Creates a new statistics. Only users with the Standard Registry role are + allowed to make the request. + parameters: [] + requestBody: + required: true + description: Configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: &ref_27 + - policy-statistics + security: + - bearer: [] + get: + operationId: PolicyStatisticsApi_getStatistics + summary: Return a list of all statistics. + description: >- + Returns all statistics. Only users with the Standard Registry role are + allowed to make the request. + 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 + 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/StatisticsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_27 + security: + - bearer: [] /worker-tasks: get: operationId: WorkerTasksController_getAllWorkerTasks @@ -12024,7 +12110,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: &ref_27 + tags: &ref_28 - worker-tasks security: - bearer: [] @@ -12047,7 +12133,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_27 + tags: *ref_28 security: - bearer: [] /worker-tasks/delete/{taskId}: @@ -12076,7 +12162,7 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: *ref_27 + tags: *ref_28 security: - bearer: [] info: @@ -12523,19 +12609,19 @@ components: example: username role: type: string - enum: &ref_36 + enum: &ref_37 - STANDARD_REGISTRY - USER - AUDITOR example: USER permissionsGroup: - example: &ref_37 + example: &ref_38 - {} type: array items: type: string permissions: - example: &ref_38 + example: &ref_39 - POLICIES_POLICY_READ type: array items: @@ -12720,35 +12806,35 @@ components: type: object properties: idLvl: - oneOf: &ref_28 + oneOf: &ref_29 - type: string - type: number - enum: &ref_29 + enum: &ref_30 - 0 - 1 example: 0 eventsLvl: - oneOf: &ref_30 + oneOf: &ref_31 - type: string - type: number - enum: &ref_31 + enum: &ref_32 - 0 - 1 example: 0 propLvl: - oneOf: &ref_32 + oneOf: &ref_33 - type: string - type: number - enum: &ref_33 + enum: &ref_34 - 0 - 1 - 2 example: 0 childrenLvl: - oneOf: &ref_34 + oneOf: &ref_35 - type: string - type: number - enum: &ref_35 + enum: &ref_36 - 0 - 1 - 2 @@ -12812,20 +12898,20 @@ components: type: object properties: idLvl: - oneOf: *ref_28 - enum: *ref_29 + oneOf: *ref_29 + enum: *ref_30 example: 0 eventsLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 propLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 childrenLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 moduleId1: type: string @@ -12939,20 +13025,20 @@ components: type: object properties: idLvl: - oneOf: *ref_28 - enum: *ref_29 + oneOf: *ref_29 + enum: *ref_30 example: 0 eventsLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 propLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 childrenLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 documentId1: type: string @@ -12987,20 +13073,20 @@ components: type: object properties: idLvl: - oneOf: *ref_28 - enum: *ref_29 + oneOf: *ref_29 + enum: *ref_30 example: 0 eventsLvl: - oneOf: *ref_30 - enum: *ref_31 + oneOf: *ref_31 + enum: *ref_32 example: 0 propLvl: - oneOf: *ref_32 - enum: *ref_33 + oneOf: *ref_33 + enum: *ref_34 example: 0 childrenLvl: - oneOf: *ref_34 - enum: *ref_35 + oneOf: *ref_35 + enum: *ref_36 example: 0 toolId1: type: string @@ -13782,15 +13868,15 @@ components: example: username role: type: string - enum: *ref_36 + enum: *ref_37 example: USER permissionsGroup: - example: *ref_37 + example: *ref_38 type: array items: type: string permissions: - example: *ref_38 + example: *ref_39 type: array items: type: string @@ -15104,7 +15190,7 @@ components: #did:hedera:testnet:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_0.0.0000001 permissions: type: string - enum: &ref_39 + enum: &ref_40 - ANALYTIC_POLICY_READ - ANALYTIC_MODULE_READ - ANALYTIC_TOOL_READ @@ -15153,6 +15239,8 @@ components: - PERMISSIONS_ROLE_UPDATE - PERMISSIONS_ROLE_DELETE - PERMISSIONS_ROLE_MANAGE + - STATISTICS_STATISTIC_READ + - STATISTICS_STATISTIC_CREATE - ACCESS_POLICY_ALL - ACCESS_POLICY_ASSIGNED - ACCESS_POLICY_PUBLISHED @@ -15171,7 +15259,7 @@ components: properties: name: type: string - enum: *ref_39 + enum: *ref_40 example: ANALYTIC_POLICY_READ category: type: string @@ -15198,6 +15286,7 @@ components: - PERMISSIONS - ACCESS - DELEGATION + - STATISTICS example: ANALYTIC entity: type: string @@ -15235,6 +15324,7 @@ components: - TOKEN - TRUST_CHAIN - ROLE + - STATISTIC example: POLICY action: type: string @@ -15283,6 +15373,23 @@ components: required: - policyIds - assign + StatisticsDTO: + 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 + required: + - name WorkersTasksDTO: type: object properties: From 03efac47f03128383a5a45ce1d50c703084c10a2 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 2 Sep 2024 19:37:54 +0400 Subject: [PATCH 04/48] update Signed-off-by: Stepan Kiryakov --- ...ew-policy-statistics-dialog.component.html | 3 +- .../policy-statistics.component.html | 3 +- .../policy-statistics.component.ts | 1 + .../src/app/themes/guardian/dropdown.scss | 54 +++++++++++++++++-- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html index ac266c0268..d5d062de6a 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.html @@ -31,7 +31,8 @@ [showClear]="false" class="guardian-dropdown" appendTo="body" - placeholder="Not selected"> + placeholder="Not selected" + panelStyleClass="guardian-dropdown-panel">
Policy diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html index 0e20fd2ebf..3f37924305 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html @@ -38,7 +38,8 @@ [options]="allPolicies" [showClear]="false" class="guardian-dropdown" - placeholder="Select Policy"> + placeholder="Select Policy" + panelStyleClass="guardian-dropdown-panel">
Policy diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts index 1a0433ffdd..17c6b2ab5b 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts @@ -120,6 +120,7 @@ export class PolicyStatisticsComponent implements OnInit { name: 'All', instanceTopicId: null }); + this.allPolicies.forEach((p: any) => p.label = p.name); const topic = this.route.snapshot.queryParams['topic']; this.currentPolicy = diff --git a/frontend/src/app/themes/guardian/dropdown.scss b/frontend/src/app/themes/guardian/dropdown.scss index acc9142634..bd536c7b87 100644 --- a/frontend/src/app/themes/guardian/dropdown.scss +++ b/frontend/src/app/themes/guardian/dropdown.scss @@ -21,7 +21,7 @@ padding: 8px 16px; } - .p-dropdown-trigger { + .p-dropdown-trigger { .p-dropdown-trigger-icon { font-size: 12px; color: var(--guardian-font-color, #23252E); @@ -53,7 +53,55 @@ } } - .guardian-dropdown-item { - + .guardian-dropdown-item {} +} + +.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: 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; + } } } \ No newline at end of file From a86ee04a85989954bf0dc6fe6aa12c84765c4769 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 3 Sep 2024 16:12:49 +0400 Subject: [PATCH 05/48] update Signed-off-by: Stepan Kiryakov --- .../src/database-modules/database-server.ts | 77 +++++++++++++++++++ common/src/entity/index.ts | 3 +- common/src/entity/policy-statistic.ts | 75 ++++++++++++++++++ .../src/api/statistics.service.ts | 29 ++++--- guardian-service/src/app.ts | 5 +- 5 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 common/src/entity/policy-statistic.ts diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index b5cb29f675..cb468b4700 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -37,6 +37,7 @@ import { RetirePool, AssignEntity, PolicyTest, + PolicyStatistic } from '../entity/index.js'; import { Binary } from 'bson'; import { @@ -119,6 +120,7 @@ export class DatabaseServer { this.classMap.set(PolicyProperty, 'PolicyProperties'); this.classMap.set(MintRequest, 'MintRequest'); this.classMap.set(MintTransaction, 'MintTransaction'); + this.classMap.set(PolicyStatistic, 'PolicyStatistic'); } /** @@ -3650,4 +3652,79 @@ export class DatabaseServer { public static async removePolicyTests(tests: PolicyTest[]): Promise { await new DataBaseHelper(PolicyTest).remove(tests); } + + + + + + + + + + + + + + + + + + /** + * Create statistic + * @param statistic + */ + public static async createStatistic(statistic: any): Promise { + const item = new DataBaseHelper(PolicyStatistic).create(statistic); + return await new DataBaseHelper(PolicyStatistic).save(item); + } + + /** + * Get statistics + * @param filters + * @param options + */ + public static async getPolicyStatisticsAndCount(filters?: any, options?: any): Promise<[PolicyStatistic[], number]> { + return await new DataBaseHelper(PolicyStatistic).findAndCount(filters, options); + } + + /** + * Get statistic By ID + * @param uuid + */ + public static async gePolicyStatisticById(id: string): Promise { + return await new DataBaseHelper(PolicyStatistic).findOne(id); + } + + /** + * Get statistic + * @param filters + */ + public static async getPolicyStatistic(filters: any): Promise { + return await new DataBaseHelper(PolicyStatistic).findOne(filters); + } + + /** + * Delete statistic + * @param statistic + */ + public static async removePolicyStatistic(statistic: PolicyStatistic): Promise { + return await new DataBaseHelper(PolicyStatistic).remove(statistic); + } + + /** + * Get statistics + * @param filters + * @param options + */ + public static async getPolicyStatistics(filters?: any, options?: any): Promise { + return await new DataBaseHelper(PolicyStatistic).find(filters, options); + } + + /** + * Update statistic + * @param row + */ + public static async updatePolicyStatistic(row: PolicyStatistic): Promise { + return await new DataBaseHelper(PolicyStatistic).update(row); + } } diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index 833e1772d6..1905faa08e 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -42,4 +42,5 @@ export * from './policy-cache-data.js'; export * from './policy-cache.js'; export * from './assign-entity.js'; export * from './policy-test.js'; -export * from './log.js'; \ No newline at end of file +export * from './log.js'; +export * from './policy-statistic.js'; \ No newline at end of file diff --git a/common/src/entity/policy-statistic.ts b/common/src/entity/policy-statistic.ts new file mode 100644 index 0000000000..ebb540fe92 --- /dev/null +++ b/common/src/entity/policy-statistic.ts @@ -0,0 +1,75 @@ +import { BeforeCreate, Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from '../models/index.js'; +import { GenerateUUIDv4 } from '@guardian/interfaces'; + +/** + * PolicyStatistic collection + */ +@Entity() +export class PolicyStatistic extends BaseEntity { + /** + * Tag id + */ + @Property() + uuid?: string; + + /** + * Tag label + */ + @Property({ nullable: true }) + name?: string; + + /** + * Tag description + */ + @Property({ nullable: true }) + description?: string; + + /** + * Tag owner + */ + @Property({ + nullable: true, + index: true + }) + owner?: string; + + /** + * Target ID + */ + @Property({ nullable: true }) + status?: 'Draft' | 'Published'; + + /** + * Topic id + */ + @Property({ + nullable: true, + index: true + }) + topicId?: string; + + /** + * Message id + */ + @Property({ nullable: true }) + messageId?: string; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Set policy defaults + */ + @BeforeCreate() + setDefaults() { + this.uuid = this.uuid || GenerateUUIDv4(); + this.status = this.status || 'Draft'; + } +} diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index 7d74326228..99d2c4bb49 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -20,9 +20,18 @@ export async function statisticsAPI(logger: PinoLogger): Promise { throw new Error('Invalid Params'); } const { statistic, owner } = msg; - - - return new MessageResponse({}); + if (statistic) { + delete statistic._id; + delete statistic.id; + delete statistic.status; + delete statistic.owner; + delete statistic.messageId; + } + statistic.creator = owner.creator; + statistic.owner = owner.owner; + statistic.status = 'Draft'; + const row = await DatabaseServer.createStatistic(statistic); + return new MessageResponse(row); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); @@ -56,13 +65,13 @@ export async function statisticsAPI(logger: PinoLogger): Promise { 'name', 'description' ]; - // const [items, count] = await DatabaseServer.getToolsAndCount( - // { - // owner: owner.owner - // }, - // otherOptions - // ); - return new MessageResponse({ items: [{}], count: 1 }); + const [items, count] = await DatabaseServer.getPolicyStatisticsAndCount( + { + owner: owner.owner + }, + otherOptions + ); + return new MessageResponse({ items, count }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index bc840ed663..d5c03c6855 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -11,7 +11,7 @@ import { Contract, DataBaseHelper, DatabaseServer, DidDocument, DocumentState, DryRun, DryRunFiles, Environment, ExternalDocument, ExternalEventChannel, IPFS, LargePayloadContainer, MessageBrokerChannel, MessageServer, Migration, MintRequest, MintTransaction, mongoForLoggingInitialization, MultiDocuments, MultiPolicy, MultiPolicyTransaction, OldSecretManager, PinoLogger, pinoLoggerInitialization, Policy, PolicyCache, PolicyCacheData, PolicyCategory, - PolicyInvitations, PolicyModule, PolicyProperty, PolicyRoles, PolicyTest, PolicyTool, Record, RetirePool, RetireRequest, Schema, SecretManager, + PolicyInvitations, PolicyModule, PolicyProperty, PolicyRoles, PolicyStatistic, PolicyTest, PolicyTool, Record, RetirePool, RetireRequest, Schema, SecretManager, Settings, SplitDocuments, SuggestionsConfig, Tag, TagCache, Theme, Token, Topic, TopicMemo, TransactionLogger, TransactionLogLvl, Users, ValidateConfiguration, VcDocument, VpDocument, Wallet, WiperRequest, Workers } from '@guardian/common'; @@ -95,7 +95,8 @@ const necessaryEntity = [ PolicyCacheData, PolicyCache, AssignEntity, - PolicyTest + PolicyTest, + PolicyStatistic ] Promise.all([ From ffa02fdbe0aa3ed0e3457b42985190ebf103f3b6 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 3 Sep 2024 18:48:10 +0400 Subject: [PATCH 06/48] update Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 92 ++++++++++++++++++- api-gateway/src/helpers/guardians.ts | 21 ++++- common/src/entity/policy-statistic.ts | 8 +- frontend/src/app/app-routing.module.ts | 9 ++ ...cy-statistics-configuration.component.html | 28 ++++++ ...cy-statistics-configuration.component.scss | 18 ++++ ...licy-statistics-configuration.component.ts | 87 ++++++++++++++++++ .../policy-statistics.module.ts | 2 + .../policy-statistics.component.html | 16 +++- .../policy-statistics.component.ts | 14 ++- .../app/services/policy-statistics.service.ts | 36 +++++++- .../src/api/statistics.service.ts | 44 ++++++++- .../src/type/messages/message-api.type.ts | 2 + 13 files changed, 365 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index 8e85119018..7075b0c2c7 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -1,8 +1,8 @@ import { IAuthUser, PinoLogger } from '@guardian/common'; -import { Body, Controller, Get, HttpCode, HttpException, HttpStatus, Post, Query, Req, Response } from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, HttpException, HttpStatus, Param, Post, Query, Req, Response } from '@nestjs/common'; import { Permissions } from '@guardian/interfaces'; -import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels } from '@nestjs/swagger'; -import { InternalServerErrorDTO, StatisticsDTO, pageHeader } from '#middlewares'; +import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; +import { Examples, InternalServerErrorDTO, StatisticsDTO, pageHeader } from '#middlewares'; import { UseCache, Guardians, InternalException, ONLY_SR, EntityOwner, CacheService } from '#helpers'; import { AuthUser, Auth } from '#auth'; @@ -109,4 +109,90 @@ export class PolicyStatisticsApi { await InternalException(error, this.logger); } } + + /** + * Get statistic by id + */ + @Get('/:id') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Retrieves statistic configuration.', + description: 'Retrieves statistic configuration for the specified ID.' + ONLY_SR + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Statistic ID', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: StatisticsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + @UseCache() + async getStatisticById( + @AuthUser() user: IAuthUser, + @Param('id') id: string + ): Promise { + try { + if (!id) { + throw new HttpException('Invalid id', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getStatisticById(id, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get relationships by id + */ + @Get('/:id/relationships') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Retrieves statistic relationships.', + description: 'Retrieves statistic relationships for the specified ID.' + ONLY_SR + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Statistic ID', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: StatisticsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + @UseCache() + async getStatisticRelationships( + @AuthUser() user: IAuthUser, + @Param('id') id: string + ): Promise { + try { + if (!id) { + throw new HttpException('Invalid id', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getStatisticRelationships(id, 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 817f41c0ba..b035f50852 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -2850,7 +2850,7 @@ export class Guardians extends NatsService { } /** - * Return tools + * Return statistics * * @param {IFilter} [params] * @@ -2860,4 +2860,23 @@ export class Guardians extends NatsService { return await this.sendMessage(MessageAPI.GET_STATISTICS, { filters, owner }); } + /** + * Get statistic + * @param id + * @param owner + * @returns Operation Success + */ + public async getStatisticById(id: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_STATISTIC, { id, owner }); + } + + /** + * Get relationships + * @param id + * @param owner + * @returns Operation Success + */ + public async getStatisticRelationships(id: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_STATISTIC_RELATIONSHIPS, { id, owner }); + } } diff --git a/common/src/entity/policy-statistic.ts b/common/src/entity/policy-statistic.ts index ebb540fe92..56d46ce3a9 100644 --- a/common/src/entity/policy-statistic.ts +++ b/common/src/entity/policy-statistic.ts @@ -10,7 +10,7 @@ export class PolicyStatistic extends BaseEntity { /** * Tag id */ - @Property() + @Property({ nullable: true }) uuid?: string; /** @@ -34,6 +34,12 @@ export class PolicyStatistic extends BaseEntity { }) owner?: string; + /** + * Tool creator + */ + @Property({ nullable: true }) + creator?: string; + /** * Target ID */ diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 460284e7a0..00844a6ace 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -46,6 +46,7 @@ import { UsersManagementDetailComponent } from './views/user-management-detail/u import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.component'; import { MapService } from './services/map.service'; import { PolicyStatisticsComponent } from './modules/policy-statistics/policy-statistics/policy-statistics.component'; +import { PolicyStatisticsConfigurationComponent } from './modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component'; @Injectable({ providedIn: 'root' @@ -498,6 +499,14 @@ const routes: Routes = [ roles: [UserRole.USER] } }, + { + path: 'policy-statistics/:id', + component: PolicyStatisticsConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [UserRole.USER] + } + }, { path: '', component: HomeComponent }, { path: 'info', component: InfoComponent }, diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html new file mode 100644 index 0000000000..3d04a988f1 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -0,0 +1,28 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss new file mode 100644 index 0000000000..007c9d9fcd --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -0,0 +1,18 @@ +.policy-name { + 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; + } +} diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts new file mode 100644 index 0000000000..d9ac741c20 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { 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'; + +@Component({ + selector: 'app-policy-statistics-configuration', + templateUrl: './policy-statistics-configuration.component.html', + styleUrls: ['./policy-statistics-configuration.component.scss'], +}) +export class PolicyStatisticsConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + + public id: string; + public item: any; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private policyStatisticsService: PolicyStatisticsService, + 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.id = this.route.snapshot.params['id']; + this.loading = true; + forkJoin([ + this.policyStatisticsService.getItem(this.id), + this.policyStatisticsService.getRelationships(this.id), + ]).subscribe(([item, relationships]) => { + this.item = item; + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-statistics']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index f80d4e1d7f..0f0e834959 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -13,10 +13,12 @@ import { TableModule } from 'primeng/table'; import { TooltipModule } from 'primeng/tooltip'; import { DropdownModule } from 'primeng/dropdown'; import { NewPolicyStatisticsDialog } from './dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; +import { PolicyStatisticsConfigurationComponent } from './policy-statistics-configuration/policy-statistics-configuration.component'; @NgModule({ declarations: [ PolicyStatisticsComponent, + PolicyStatisticsConfigurationComponent, NewPolicyStatisticsDialog ], imports: [ diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html index 3f37924305..38da0805a6 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.html @@ -103,8 +103,22 @@ pTooltip="{{row[column.id]}}" [tooltipDisabled]="!column.tooltip" tooltipPosition="top" + [ngSwitch]="column.id" > - {{row[column.id]}} + +
+ + +
+
+ + {{row[column.id]}} + diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts index 17c6b2ab5b..d29775880a 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics/policy-statistics.component.ts @@ -140,9 +140,17 @@ export class PolicyStatisticsComponent implements OnInit { } private loadData() { + const filters: any = {}; + if (this.currentPolicy?.instanceTopicId) { + filters.topicId = this.currentPolicy?.instanceTopicId; + } this.loading = true; this.policyStatisticsService - .page(this.pageIndex, this.pageSize) + .page( + this.pageIndex, + this.pageSize, + filters + ) .subscribe((response) => { const { page, count } = this.policyStatisticsService.parsePage(response); this.page = page; @@ -208,4 +216,8 @@ export class PolicyStatisticsComponent implements OnInit { this.loading = false; }); } + + public onEdit(item: any) { + this.router.navigate(['/policy-statistics', item.id]); + } } diff --git a/frontend/src/app/services/policy-statistics.service.ts b/frontend/src/app/services/policy-statistics.service.ts index 896d08a05f..3c95eb6887 100644 --- a/frontend/src/app/services/policy-statistics.service.ts +++ b/frontend/src/app/services/policy-statistics.service.ts @@ -13,11 +13,33 @@ export class PolicyStatisticsService { constructor(private http: HttpClient) { } - public page(pageIndex?: number, pageSize?: number): Observable> { + public static getOptions( + filters: any, + pageIndex?: number, + pageSize?: number + ): HttpParams { + let params = new HttpParams(); + if (filters && typeof filters === 'object') { + for (const key of Object.keys(filters)) { + if (filters[key]) { + params = params.set(key, filters[key]); + } + } + } if (Number.isInteger(pageIndex) && Number.isInteger(pageSize)) { - return this.http.get(`${this.url}?pageIndex=${pageIndex}&pageSize=${pageSize}`, { observe: 'response' }); + params = params.set('pageIndex', String(pageIndex)); + params = params.set('pageSize', String(pageSize)); } - return this.http.get(`${this.url}`, { observe: 'response' }); + return params; + } + + public page( + pageIndex?: number, + pageSize?: number, + filters?: any + ): Observable> { + const params = PolicyStatisticsService.getOptions(filters, pageIndex, pageSize); + return this.http.get(`${this.url}`, { observe: 'response', params }); } public parsePage(response: HttpResponse) { @@ -29,4 +51,12 @@ export class PolicyStatisticsService { public create(policy: any): Observable { return this.http.post(`${this.url}/`, policy); } + + public getItem(id: string): Observable { + return this.http.get(`${this.url}/${id}`); + } + + public getRelationships(id: string): Observable { + return this.http.get(`${this.url}/${id}/relationships`); + } } diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index 62511bccaa..f47b52d89c 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -60,10 +60,14 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } otherOptions.fields = [ 'id', - 'creator', + // 'creator', 'owner', 'name', - 'description' + 'description', + 'status', + 'topicId', + 'messageId', + 'policyId' ]; const [items, count] = await DatabaseServer.getStatisticsAndCount( { @@ -77,4 +81,40 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError(error); } }); + + ApiResponse(MessageAPI.GET_STATISTIC, + async (msg: { id: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid load tools parameter'); + } + const { id, owner } = msg; + const item = await DatabaseServer.getStatisticById(id); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + return new MessageResponse(item); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + ApiResponse(MessageAPI.GET_STATISTIC_RELATIONSHIPS, + async (msg: { id: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid load tools parameter'); + } + const { id, owner } = msg; + const item = await DatabaseServer.getStatisticById(id); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + return new MessageResponse(item); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); } diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 6fd54678cb..1f856f976f 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -219,6 +219,8 @@ export enum MessageAPI { CHECK_KEY_PERMISSIONS = 'CHECK_KEY_PERMISSIONS', CREATE_STATISTIC = 'CREATE_STATISTIC', GET_STATISTICS = 'GET_STATISTICS', + GET_STATISTIC = 'GET_STATISTIC', + GET_STATISTIC_RELATIONSHIPS = 'GET_STATISTIC_RELATIONSHIPS', } /** From 2898e329aed37da6bf18e6082b3fd313acee0a16 Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Tue, 3 Sep 2024 14:51:06 +0000 Subject: [PATCH 07/48] [skip ci] Add swagger.yaml --- swagger.yaml | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 78181f7f14..177fcb60f1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -12064,6 +12064,76 @@ paths: tags: *ref_27 security: - bearer: [] + /policy-statistics/{id}: + get: + operationId: PolicyStatisticsApi_getStatisticById + summary: Retrieves statistic configuration. + description: >- + Retrieves statistic configuration for the specified ID. Only users with + the Standard Registry role are allowed to make the request. + parameters: + - name: id + required: true + in: path + description: Statistic ID + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_27 + security: + - bearer: [] + /policy-statistics/{id}/relationships: + get: + operationId: PolicyStatisticsApi_getStatisticRelationships + summary: Retrieves statistic relationships. + description: >- + Retrieves statistic relationships for the specified ID. Only users with + the Standard Registry role are allowed to make the request. + parameters: + - name: id + required: true + in: path + description: Statistic ID + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_27 + security: + - bearer: [] /worker-tasks: get: operationId: WorkerTasksController_getAllWorkerTasks From 2133dd8dc7fe6cac283f1315735a4ccaf819cf00 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 4 Sep 2024 18:40:37 +0400 Subject: [PATCH 08/48] update Signed-off-by: Stepan Kiryakov --- .../src/database-modules/database-server.ts | 1 + common/src/entity/policy-statistic.ts | 9 + .../new-policy-statistics-dialog.component.ts | 8 +- ...cy-statistics-configuration.component.html | 4 + ...licy-statistics-configuration.component.ts | 46 +++- .../policy-statistics.module.ts | 4 +- .../tree-graph/tree-graph.component.html | 23 ++ .../tree-graph/tree-graph.component.scss | 30 +++ .../tree-graph/tree-graph.component.ts | 207 ++++++++++++++++++ .../src/api/statistics.service.ts | 19 +- 10 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index 968ee0c48e..454ea5e60f 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -3071,6 +3071,7 @@ export class DatabaseServer extends AbstractDatabaseServer { * @param id */ public static async getContractById(id: string): Promise { + if(!id) return null; return await new DataBaseHelper(ContractCollection).findOne(id); } diff --git a/common/src/entity/policy-statistic.ts b/common/src/entity/policy-statistic.ts index 56d46ce3a9..bb2782f2a7 100644 --- a/common/src/entity/policy-statistic.ts +++ b/common/src/entity/policy-statistic.ts @@ -70,6 +70,15 @@ export class PolicyStatistic extends BaseEntity { }) policyId?: string; + /** + * Policy Topic id + */ + @Property({ + nullable: true, + index: true + }) + instanceTopicId?: string; + /** * Set policy defaults */ diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts index 103606962c..f67c9a67f7 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.ts @@ -49,7 +49,13 @@ export class NewPolicyStatisticsDialog { public onSubmit(): void { if (this.dataForm.valid) { - this.ref.close(this.dataForm.value); + const { name, description, policy } = this.dataForm.value; + this.ref.close({ + name, + description, + policyId: policy?.id, + instanceTopicId: policy?.instanceTopicId, + }); } } } diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 3d04a988f1..621bd2d0c2 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -25,4 +25,8 @@
{{title}}
+ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index d9ac741c20..bec192fc85 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { UserPermissions } from '@guardian/interfaces'; +import { 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 { TreeNode, TreeGraphComponent } from '../tree-graph/tree-graph.component'; @Component({ selector: 'app-policy-statistics-configuration', @@ -20,8 +21,12 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public id: string; public item: any; + public policy: any; + public schemas: any[]; private subscription = new Subscription(); + private tree: TreeGraphComponent; + private nodes: TreeNode[]; constructor( private profileService: ProfileService, @@ -73,6 +78,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { this.policyStatisticsService.getRelationships(this.id), ]).subscribe(([item, relationships]) => { this.item = item; + if (relationships) { + this.prepareData(relationships); + } setTimeout(() => { this.loading = false; }, 500); @@ -81,7 +89,43 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { }); } + private prepareData(relationships: any) { + this.policy = relationships.policy || {}; + this.schemas = relationships.schemas || []; + this.nodes = []; + for (const schema of this.schemas) { + try { + const item = new Schema(schema); + const node = new TreeNode(item.iri); + node.type = item.entity === 'VC' ? 'root' : 'sub'; + node.data = { + name: item.name, + description: item.description, + } + for (const field of item.fields) { + if (field.isRef && field.type) { + node.addId(field.type) + } + } + this.nodes.push(node); + } catch (error) { + console.log(error); + } + } + + if (this.tree) { + this.tree.setData(this.nodes) + } + } + public onBack() { this.router.navigate(['/policy-statistics']); } + + public initTree($event: TreeGraphComponent) { + this.tree = $event; + if (this.nodes) { + this.tree.setData(this.nodes) + } + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index 0f0e834959..bd47aad83a 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -14,12 +14,14 @@ import { TooltipModule } from 'primeng/tooltip'; import { DropdownModule } from 'primeng/dropdown'; import { NewPolicyStatisticsDialog } from './dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; import { PolicyStatisticsConfigurationComponent } from './policy-statistics-configuration/policy-statistics-configuration.component'; +import { TreeGraphComponent } from './tree-graph/tree-graph.component'; @NgModule({ declarations: [ PolicyStatisticsComponent, PolicyStatisticsConfigurationComponent, - NewPolicyStatisticsDialog + NewPolicyStatisticsDialog, + TreeGraphComponent ], imports: [ CommonModule, diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html new file mode 100644 index 0000000000..9042664930 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html @@ -0,0 +1,23 @@ +
+ +
+
+
+ {{node.data.name}} +
+
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss new file mode 100644 index 0000000000..52aa56a0f6 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss @@ -0,0 +1,30 @@ +.tree-graph { + // width: 400px; + // height: 400px; + display: block; + position: relative; + overflow: scroll; + + .tree-grid { + display: grid; + + .tree-node-container { + width: 100%; + height: 100%; + padding: 24px; + display: flex; + justify-content: center; + align-items: flex-start; + + .tree-node { + background: #ffffff; + border: 1px solid #cacaca; + box-shadow: 0 2px 15px 2px #cacaca; + padding: 16px; + border-radius: 8px; + white-space: nowrap; + width: fit-content; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts new file mode 100644 index 0000000000..44933960c6 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts @@ -0,0 +1,207 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { GenerateUUIDv4 } from '@guardian/interfaces'; + +export class TreeNode { + public readonly uuid: string; + + public id: string; + public type: 'root' | 'sub'; + public childIds: string[]; + public children: TreeNode[]; + public size: number; + public data: any; + public row: number; + public column: number; + public minColumn: number; + public maxColumn: number; + public minRow: number; + public maxRow: number; + + constructor(id?: string) { + this.uuid = GenerateUUIDv4(); + + this.id = id || this.uuid; + this.type = 'root'; + this.childIds = []; + this.children = []; + this.size = 1; + this.data = null; + this.row = 0; + this.column = 0; + this.minColumn = 0; + this.maxColumn = 0; + this.minRow = 0; + this.maxRow = 0; + } + + public addId(id: string): void { + this.childIds.push(id); + } + + public addNode(node: TreeNode): void { + this.children.push(node); + } + + public clone(): TreeNode { + const clone = new TreeNode(this.id); + clone.type = this.type; + clone.data = this.data; + clone.childIds = this.childIds.slice(); + return clone; + } + + public resize(): void { + let size = 0; + for (const child of this.children) { + child.resize(); + size += child.size; + } + this.size = Math.max(1, size); + if (this.size % 2 === 0) { + this.size += 1; + } + } +} + +export class Grid { + public column: number; + public row: number; + public nodes: TreeNode[]; + public columnsTemplate: string; + public rowsTemplate: string; + + constructor() { + this.column = 0; + this.row = 0; + this.nodes = []; + } + + public addNode(node: TreeNode): void { + this.row = Math.max(this.row, node.row); + this.column = Math.max(this.column, node.column); + this.nodes.push(node); + } + + public render() { + this.columnsTemplate = 'auto'; + for (let i = 1; i < this.column; i++) { + this.columnsTemplate += ` auto`; + } + this.rowsTemplate = 'auto'; + for (let i = 1; i < this.row; i++) { + this.rowsTemplate += ` auto`; + } + } +} + +@Component({ + selector: 'app-tree-graph', + templateUrl: './tree-graph.component.html', + styleUrls: ['./tree-graph.component.scss'], +}) +export class TreeGraphComponent implements OnInit { + @Output('init') init = new EventEmitter(); + public roots: TreeNode[]; + public grid: Grid; + + constructor() { + } + + ngOnInit() { + this.init.emit(this); + } + + ngOnDestroy(): void { + + } + + public setData(nodes: TreeNode[]) { + this.roots = this.nonUniqueNodes(nodes) + this.grid = this.createLayout(this.roots); + this.grid.render(); + + // const roots = this.uniqueNodes(nodes) + // for (const node of nodes) { + // node.size = 1; + // if (!node.children) { + // node.children = []; + // } + // for (const id of node.childIds) { + // const child = nodeMap.get(id); + // if (child) { + // node.children.push(child); + // } + // } + // } + // const roots = nodes.filter((n) => n.type === 'root') + } + + private uniqueNodes(nodes: TreeNode[]): TreeNode[] { + const nodeMap = new Map(); + for (const node of nodes) { + nodeMap.set(node.id, node); + } + + const roots = nodes.filter((n) => n.type === 'root'); + const subs = nodes.filter((n) => n.type !== 'root'); + + return roots; + } + + private nonUniqueNodes(nodes: TreeNode[]): TreeNode[] { + const roots = nodes.filter((n) => n.type === 'root'); + const subs = nodes.filter((n) => n.type !== 'root'); + + const nodeMap = new Map(); + for (const node of subs) { + nodeMap.set(node.id, node); + } + + const getSubNode = (node: TreeNode): TreeNode => { + for (const id of node.childIds) { + const child = nodeMap.get(id); + if (child) { + node.addNode(getSubNode(child.clone())); + } + } + return node; + } + for (const root of roots) { + getSubNode(root); + root.resize(); + } + return roots; + } + + private createLayout(roots: TreeNode[]): Grid { + const grid = new Grid(); + const updateCoord = (node: TreeNode, row: number, column: number): number => { + const max = column + node.size; + node.column = column + Math.floor(node.size / 2) + 1; + node.minColumn = column + 1; + node.maxColumn = max + 1; + node.row = row; + node.minRow = row; + node.maxRow = row; + grid.addNode(node) + + const m = (node.children.length / 2); + let c = column; + for (let index = 0; index < node.children.length; index++) { + const child = node.children[index]; + if (Math.abs(index - m) < 0.1) { + c++; + } + c = updateCoord(child, row + 1, c); + } + return max; + } + + let column = 0; + for (const node of roots) { + column = updateCoord(node, 1, column); + } + + return grid; + } +} diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index f47b52d89c..f8fe51ed5a 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -1,6 +1,6 @@ import { ApiResponse } from './helpers/api-response.js'; -import { DatabaseServer, MessageError, MessageResponse, PinoLogger } from '@guardian/common'; -import { IOwner, MessageAPI } from '@guardian/interfaces'; +import { DatabaseServer, ImportExportUtils, MessageError, MessageResponse, PinoLogger, PolicyImportExport } from '@guardian/common'; +import { IOwner, MessageAPI, PolicyType, SchemaStatus } from '@guardian/interfaces'; /** * Connect to the message broker methods of working with statistics. @@ -111,7 +111,20 @@ export async function statisticsAPI(logger: PinoLogger): Promise { if (!item || item.owner !== owner.owner) { return new MessageError('Item does not exist.'); } - return new MessageResponse(item); + const policyId = item.policyId; + console.log('1') + const policy = await DatabaseServer.getPolicyById(policyId); + console.log('2') + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + console.log('3') + const { schemas } = await PolicyImportExport.loadPolicyComponents(policy); + console.log('4') + return new MessageResponse({ + policy, + schemas: schemas.filter((s) => s.status === SchemaStatus.PUBLISHED) + }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); From 156e7e71c5312682a1083a08fe31041d355e9b1a Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 6 Sep 2024 18:05:17 +0400 Subject: [PATCH 09/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 132 +++++++- ...cy-statistics-configuration.component.scss | 297 +++++++++++++++++- ...licy-statistics-configuration.component.ts | 19 +- .../policy-statistics.module.ts | 4 + .../tree-graph/tree-graph.component.html | 55 +++- .../tree-graph/tree-graph.component.scss | 193 +++++++++++- .../tree-graph/tree-graph.component.ts | 237 ++++++-------- .../policy-statistics/tree-graph/tree-grid.ts | 95 ++++++ .../policy-statistics/tree-graph/tree-line.ts | 64 ++++ .../policy-statistics/tree-graph/tree-list.ts | 119 +++++++ .../policy-statistics/tree-graph/tree-node.ts | 101 ++++++ .../tree-graph/tree-types.ts | 7 + frontend/src/app/themes/guardian/index.scss | 1 + frontend/src/app/themes/guardian/input.scss | 25 +- frontend/src/app/themes/guardian/tabs.scss | 47 +++ .../src/api/statistics.service.ts | 9 +- 16 files changed, 1202 insertions(+), 203 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-types.ts create mode 100644 frontend/src/app/themes/guardian/tabs.scss diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 621bd2d0c2..a09767549a 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -8,25 +8,127 @@ here
-
- +
+ +
+ {{title}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}}
-
Back to Policies
- +
-
- {{title}} +
+ + + +
+
+ + +
+
+ {{node.data.name}} +
+
+ +
+
+ Nothing selected +
+
+
+
+
+ +
+
+
+ {{rootNode.data.name}} +
+
+ + +
+ +
+
+
+
+
+
+ + +
+
+
+ +
+
+ {{item.data.title}} +
+
+
+
+
+
+ + + +
+
+
+
+
+ +
+ + + + +
-
- +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index 007c9d9fcd..6682a792bb 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -1,18 +1,291 @@ -.policy-name { - color: #848FA9; - font-size: 14px; - font-weight: 500; - line-height: 16px; - position: absolute; - top: 34px; - right: 0; - - .policy-version { - padding-left: 16px; +.guardian-page { + position: relative; + padding: 0px; + + .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: #fff; + } + } + + .guardian-user-page-header { + color: #fff; + } + + + .policy-name { + color: #fff; + 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: #fff; + border-top: 1px solid #E1E7EF; + } + + .body-container { + width: 100%; + height: 100%; + + .schema-viewer { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: hidden; + + .tree-container { + position: absolute; + left: 0px; + top: 40px; + bottom: 0px; + right: 0px; + z-index: 1; + } + + .schema-search { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + height: 56px; + z-index: 2; + background: #eff3f7; + padding: 8px 416px 8px 16px; + + .guardian-search { + max-width: 500px; + } + } + + .schema-fields { + position: absolute; + top: 0px; + bottom: 0px; + right: 0px; + width: 400px; + z-index: 3; + background: #fff; + border-left: 1px solid #E1E7EF; + box-shadow: 0px 0px 4px 0px #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; + + &[hidden-schema="true"] { + width: 0px; + } + + .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: #23252E; + } + + .schema-config { + width: 100%; + height: 100%; + padding: 0px 24px 24px 24px; + } + + .field-list { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + .field-search { + padding: 24px 0px; + width: 100%; + height: 88px; + min-height: 88px; + } + + .field-tree-container { + width: 100%; + height: 100%; + position: relative; + + .field-tree { + position: absolute; + overflow: auto; + top: 0; + bottom: 0; + left: 0; + right: 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; + + .collapse-btn { + width: 20px; + height: 20px; + margin: 2px; + cursor: pointer; + } + } + + .field-select { + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + } + + .field-name { + padding-left: 8px; + } + } + } + } + } + } + } + } } } + + .guardian-dropdown { - &::ng-deep .p-dropdown { + &::ng-deep .p-dropdown { width: 250px; } } + +.tree-container { + display: flex; + + .tree-node { + background: #ffffff; + border: 1px solid #E1E7EF; + box-shadow: 0px 4px 4px 0px #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 #4169E2, 0px 6px 6px 0px #00000021; + } + + &.selected-type-sub { + border: 1px solid var(--guardian-primary-color, #4169E2); + } + + &.selected-type-hidden { + opacity: 0.4; + } + + .node-header { + width: 100%; + padding: 9px 8px 9px 16px; + background: #F9FAFC; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: #23252E; + } + + .node-fields { + border-top: 1px solid #E1E7EF; + color: #23252E; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + padding: 9px 8px 9px 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .node-not-fields { + border-top: 1px solid #E1E7EF; + color: #848FA9; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + padding: 9px 8px 9px 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index bec192fc85..e967b0ec0a 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -4,7 +4,9 @@ import { 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 { TreeNode, TreeGraphComponent } from '../tree-graph/tree-graph.component'; +import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; +import { TreeNode } from '../tree-graph/tree-node'; +import { TreeListData, TreeListItem } from '../tree-graph/tree-list'; @Component({ selector: 'app-policy-statistics-configuration', @@ -28,6 +30,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private tree: TreeGraphComponent; private nodes: TreeNode[]; + public selectedNode: TreeNode | null = null; + public rootNode: TreeNode | null = null; + constructor( private profileService: ProfileService, private policyStatisticsService: PolicyStatisticsService, @@ -99,8 +104,11 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { const node = new TreeNode(item.iri); node.type = item.entity === 'VC' ? 'root' : 'sub'; node.data = { + iri: item.iri, name: item.name, description: item.description, + fields: TreeListData.fromObject(item, 'fields'), + selectedFields: null } for (const field of item.fields) { if (field.isRef && field.type) { @@ -128,4 +136,13 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { this.tree.setData(this.nodes) } } + + public onSelectNode(node: TreeNode | null) { + this.selectedNode = node; + this.rootNode = node?.getRoot() || null; + } + + public onCollapseField(node: TreeNode, field: TreeListItem) { + (node.data.fields as TreeListData).collapse(field, !field.collapsed); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index bd47aad83a..22f6488ac5 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -15,6 +15,8 @@ import { DropdownModule } from 'primeng/dropdown'; import { NewPolicyStatisticsDialog } from './dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; import { PolicyStatisticsConfigurationComponent } from './policy-statistics-configuration/policy-statistics-configuration.component'; import { TreeGraphComponent } from './tree-graph/tree-graph.component'; +import { TabViewModule } from 'primeng/tabview'; +import { CheckboxModule } from 'primeng/checkbox'; @NgModule({ declarations: [ @@ -35,6 +37,8 @@ import { TreeGraphComponent } from './tree-graph/tree-graph.component'; TooltipModule, InputTextModule, DropdownModule, + TabViewModule, + CheckboxModule, AngularSvgIconModule.forRoot(), ], exports: [], diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html index 9042664930..c2e5ec7313 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html @@ -1,23 +1,46 @@ -
- +
-
- {{node.data.name}} +
+
+
+ +
+
+
+
+
-
-
\ No newline at end of file +
diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss index 52aa56a0f6..d1c449a11d 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.scss @@ -1,29 +1,194 @@ +:host { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; +} + +.tree-container { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; +} + .tree-graph { - // width: 400px; - // height: 400px; - display: block; + background: #EFF3F7; position: relative; - overflow: scroll; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + overflow: hidden; + -webkit-transform: translateZ(0); + transform: translateZ(0); + /* make GPU composite layer */ + will-change: transform, top, left; + /* try optimize */ + + &[moving="true"] { + cursor: grabbing; + } + + .tree-moving-container { + position: absolute; + left: 0px; + top: 0px; + -webkit-transform: translateZ(0); + transform: translateZ(0); + /* make GPU composite layer */ + will-change: transform, top, left; + /* try optimize */ + } .tree-grid { + position: absolute; + left: 0px; + top: 0px; display: grid; + background-color: #f4f6f9; + opacity: 1; + background-image: + linear-gradient(#e8e8e8 0.8px, transparent 0.8px), + linear-gradient(90deg, #e8e8e8 0.8px, transparent 0.8px), + linear-gradient(#e8e8e8 0.4px, transparent 0.4px), + linear-gradient(90deg, #e8e8e8 0.4px, #f4f6f9 0.4px); + background-size: 20px 20px, 20px 20px, 4px 4px, 4px 4px; + background-position: -0.8px -0.8px, -0.8px -0.8px, -0.4px -0.4px, -0.4px -0.4px; + .tree-node-container { width: 100%; height: 100%; - padding: 24px; display: flex; - justify-content: center; - align-items: flex-start; + justify-content: flex-start; + align-items: center; + position: relative; + padding: 25px; + flex-direction: column; .tree-node { - background: #ffffff; - border: 1px solid #cacaca; - box-shadow: 0 2px 15px 2px #cacaca; - padding: 16px; - border-radius: 8px; - white-space: nowrap; - width: fit-content; + position: relative; + } + + .tree-node-lines { + width: 100%; + height: 100%; + position: relative; + } + + .tree-node-line { + position: absolute; + top: 0px; + bottom: -50px; + min-width: 3px; + pointer-events: none; + + &.selected-type-selected { + + &::after, + &::before { + border-color: var(--guardian-primary-color, #4169E2) !important; + border-width: 2px !important; + } + + &.middle-line, + &.right-line { + &::before { + left: -2px; + } + } + + z-index: 2; + } + + &.selected-type-sub { + + &::after, + &::before { + border-color: var(--guardian-primary-color, #4169E2) !important; + } + + z-index: 1; + } + + &.selected-type-hidden { + opacity: 0.2; + z-index: 0; + } + + &.left-line { + right: 50%; + + &::before { + content: ""; + display: block; + position: absolute; + top: 0px; + right: 0px; + bottom: 25px; + left: 0px; + border-right: 1px solid #848FA9; + border-bottom: 1px solid #848FA9; + } + + &::after { + content: ""; + display: block; + position: absolute; + bottom: 0px; + left: 0px; + width: 5px; + height: 25px; + border-left: 1px solid #848FA9; + border-top: 1px solid #848FA9; + } + } + + &.middle-line { + left: 50%; + + &::before { + content: ""; + display: block; + position: absolute; + top: 0px; + right: 0px; + bottom: 0px; + left: -1px; + border-left: 1px solid #848FA9; + } + } + + &.right-line { + left: 50%; + + &::before { + content: ""; + display: block; + position: absolute; + top: 0px; + right: 0px; + bottom: 25px; + left: -1px; + border-left: 1px solid #848FA9; + border-bottom: 1px solid #848FA9; + } + + &::after { + content: ""; + display: block; + position: absolute; + bottom: 0px; + right: 0px; + width: 5px; + height: 25px; + border-right: 1px solid #848FA9; + border-top: 1px solid #848FA9; + } + } } } } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts index 44933960c6..a3fa86b89d 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts @@ -1,98 +1,7 @@ -import { Component, EventEmitter, OnInit, Output } from '@angular/core'; -import { GenerateUUIDv4 } from '@guardian/interfaces'; - -export class TreeNode { - public readonly uuid: string; - - public id: string; - public type: 'root' | 'sub'; - public childIds: string[]; - public children: TreeNode[]; - public size: number; - public data: any; - public row: number; - public column: number; - public minColumn: number; - public maxColumn: number; - public minRow: number; - public maxRow: number; - - constructor(id?: string) { - this.uuid = GenerateUUIDv4(); - - this.id = id || this.uuid; - this.type = 'root'; - this.childIds = []; - this.children = []; - this.size = 1; - this.data = null; - this.row = 0; - this.column = 0; - this.minColumn = 0; - this.maxColumn = 0; - this.minRow = 0; - this.maxRow = 0; - } - - public addId(id: string): void { - this.childIds.push(id); - } - - public addNode(node: TreeNode): void { - this.children.push(node); - } - - public clone(): TreeNode { - const clone = new TreeNode(this.id); - clone.type = this.type; - clone.data = this.data; - clone.childIds = this.childIds.slice(); - return clone; - } - - public resize(): void { - let size = 0; - for (const child of this.children) { - child.resize(); - size += child.size; - } - this.size = Math.max(1, size); - if (this.size % 2 === 0) { - this.size += 1; - } - } -} - -export class Grid { - public column: number; - public row: number; - public nodes: TreeNode[]; - public columnsTemplate: string; - public rowsTemplate: string; - - constructor() { - this.column = 0; - this.row = 0; - this.nodes = []; - } - - public addNode(node: TreeNode): void { - this.row = Math.max(this.row, node.row); - this.column = Math.max(this.column, node.column); - this.nodes.push(node); - } - - public render() { - this.columnsTemplate = 'auto'; - for (let i = 1; i < this.column; i++) { - this.columnsTemplate += ` auto`; - } - this.rowsTemplate = 'auto'; - for (let i = 1; i < this.row; i++) { - this.rowsTemplate += ` auto`; - } - } -} +import { Component, ContentChild, ElementRef, EventEmitter, OnInit, Output, TemplateRef, ViewChild } from '@angular/core'; +import { SelectType } from './tree-types'; +import { TreeNode } from './tree-node'; +import { Grid } from './tree-grid'; @Component({ selector: 'app-tree-graph', @@ -100,40 +9,60 @@ export class Grid { styleUrls: ['./tree-graph.component.scss'], }) export class TreeGraphComponent implements OnInit { - @Output('init') init = new EventEmitter(); + @ViewChild('gridEl', { static: false }) gridEl: ElementRef; + @ContentChild('nodeTemplate') nodeTemplate: TemplateRef; + + @Output('init') initEvent = new EventEmitter(); + @Output('select') selectEvent = new EventEmitter(); + public roots: TreeNode[]; public grid: Grid; + public width: number = 200; + public zoom = 1; constructor() { + } ngOnInit() { - this.init.emit(this); + this.initEvent.emit(this); } ngOnDestroy(): void { } + public get moving(): boolean { + if (this.grid) { + return this.grid.moving; + } else { + return false; + } + } + public setData(nodes: TreeNode[]) { this.roots = this.nonUniqueNodes(nodes) - this.grid = this.createLayout(this.roots); + this.grid = Grid.createLayout(this.width, this.roots); this.grid.render(); + } - // const roots = this.uniqueNodes(nodes) - // for (const node of nodes) { - // node.size = 1; - // if (!node.children) { - // node.children = []; - // } - // for (const id of node.childIds) { - // const child = nodeMap.get(id); - // if (child) { - // node.children.push(child); - // } - // } - // } - // const roots = nodes.filter((n) => n.type === 'root') + public select(node: TreeNode) { + const selected = node.selected !== SelectType.SELECTED; + if (selected) { + for (const node of this.grid.nodes) { + node.selected = SelectType.HIDDEN; + } + node.select(SelectType.SELECTED); + } else { + for (const node of this.grid.nodes) { + node.selected = SelectType.NONE; + } + } + for (const node of this.grid.nodes) { + for (const line of node.lines) { + line.select(); + } + } } private uniqueNodes(nodes: TreeNode[]): TreeNode[] { @@ -162,6 +91,8 @@ export class TreeGraphComponent implements OnInit { const child = nodeMap.get(id); if (child) { node.addNode(getSubNode(child.clone())); + } else { + console.log('', id) } } return node; @@ -173,35 +104,65 @@ export class TreeGraphComponent implements OnInit { return roots; } - private createLayout(roots: TreeNode[]): Grid { - const grid = new Grid(); - const updateCoord = (node: TreeNode, row: number, column: number): number => { - const max = column + node.size; - node.column = column + Math.floor(node.size / 2) + 1; - node.minColumn = column + 1; - node.maxColumn = max + 1; - node.row = row; - node.minRow = row; - node.maxRow = row; - grid.addNode(node) - - const m = (node.children.length / 2); - let c = column; - for (let index = 0; index < node.children.length; index++) { - const child = node.children[index]; - if (Math.abs(index - m) < 0.1) { - c++; - } - c = updateCoord(child, row + 1, c); - } - return max; + public setZoom(zoom: number, el: any) { + let transformOrigin = [0, 0]; + var p = ["webkit", "moz", "ms", "o"], + s = "scale(" + zoom + ")", + oString = (transformOrigin[0] * 100) + "% " + (transformOrigin[1] * 100) + "%"; + + for (var i = 0; i < p.length; i++) { + el.style[p[i] + "Transform"] = s; + el.style[p[i] + "TransformOrigin"] = oString; } - let column = 0; - for (const node of roots) { - column = updateCoord(node, 1, column); + el.style["transform"] = s; + el.style["transformOrigin"] = oString; + } + + public onZoom(zoom: number) { + if (zoom > 0) { + this.zoom += 0.1; + } else { + this.zoom -= 0.1; + } + this.zoom = Math.max(this.zoom, 0.1); + this.setZoom(this.zoom, this.gridEl.nativeElement); + } + + public onMouseDown($event: any) { + this.grid.onMove(true, $event); + this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + } + + public onMouseUp($event: any) { + this.grid.onMove(false, $event); + this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + } + + public onMouseMove($event: any) { + this.grid.onMoving($event); + this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + } + + public onScroll($event: any) { + if ($event.deltaY < 0) { + this.zoom = this.zoom * 1.1; + } else { + this.zoom = this.zoom * 0.9; } + this.zoom = Math.max(this.zoom, 0.1); + this.setZoom(this.zoom, this.gridEl.nativeElement); + } - return grid; + public onSelectNode(node: TreeNode) { + this.select(node); + if(node.selected === SelectType.SELECTED) { + this.selectEvent.emit(node); + } else { + this.selectEvent.emit(null); + } } } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts new file mode 100644 index 0000000000..7a320ec91a --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts @@ -0,0 +1,95 @@ +import { Line } from "./tree-line"; +import { TreeNode } from "./tree-node"; + + +export class Grid { + public column: number; + public row: number; + public nodes: TreeNode[]; + public columnsTemplate: string; + public rowsTemplate: string; + public width: number; + public x: number = 0; + public y: number = 0; + public moving: boolean = false; + + private _clientX: number = 0; + private _clientY: number = 0; + + constructor(width: number) { + this.column = 0; + this.row = 0; + this.nodes = []; + this.width = width; + } + + public addNode(node: TreeNode): void { + this.row = Math.max(this.row, node.row); + this.column = Math.max(this.column, node.column); + this.nodes.push(node); + } + + public static createLayout(width: number, roots: TreeNode[]): Grid { + const grid = new Grid(width); + const updateCoord = (node: TreeNode, row: number, column: number): number => { + const max = column + node.size; + node.column = column + (node.size / 2); + node.minColumn = column + 1; + node.maxColumn = max + 1; + node.row = row; + node.minRow = row; + node.maxRow = row; + grid.addNode(node) + + let c = column; + for (const child of node.children) { + c = updateCoord(child, row + 1, c); + } + return max; + } + + let column = 0; + for (const node of roots) { + column = updateCoord(node, 1, column); + } + + return grid; + } + + public render(): void { + this.columnsTemplate = `${this.width}px`; + for (let i = 1; i < this.column; i++) { + this.columnsTemplate += ` ${this.width}px`; + } + this.rowsTemplate = `auto`; + for (let i = 1; i < this.row; i++) { + this.rowsTemplate += ` auto `; + } + + for (const node of this.nodes) { + node.lines = []; + for (const child of node.children) { + const line = new Line(this.width); + line.addStart(node); + line.addEnd(child); + line.render(); + node.lines.push(line); + } + } + } + + public onMove(moving: boolean, $event: any) { + this.moving = moving; + this._clientX = $event.clientX; + this._clientY = $event.clientY; + } + + public onMoving($event: any) { + if (this.moving) { + this.x = this.x + (this._clientX - $event.clientX); + this.y = this.y + (this._clientY - $event.clientY); + this._clientX = $event.clientX; + this._clientY = $event.clientY; + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts new file mode 100644 index 0000000000..3d09232382 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts @@ -0,0 +1,64 @@ +import { SelectType } from "./tree-types"; +import { TreeNode } from "./tree-node"; + + +export class Line { + public width: number; + public height: number; + public items: { + x1: number; + x2: number; + y1: number; + y2: number; + }[]; + public start: TreeNode; + public end: TreeNode; + public type: string; + public selected: SelectType; + private _width: number; + + constructor(width: number) { + this._width = width; + this.items = []; + this.width = 0; + this.height = 0; + } + + public addStart(node: TreeNode): void { + this.start = node; + } + + public addEnd(node: TreeNode): void { + this.end = node; + } + + public render(): void { + const colDiff = this.end.column - this.start.column; + this.width = Math.abs(colDiff) * this._width; + if (colDiff === 0) { + this.type = 'middle'; + } else if (colDiff < 0) { + this.type = 'left'; + } else { + this.type = 'right'; + } + } + + public select() { + if (!this.start || !this.end) { + return; + } + if (this.start.selected === SelectType.HIDDEN || + this.end.selected === SelectType.HIDDEN) { + this.selected = SelectType.HIDDEN; + } else if (this.start.selected === SelectType.SELECTED || + this.end.selected === SelectType.SELECTED) { + this.selected = SelectType.SELECTED; + } else if (this.start.selected === SelectType.SUB || + this.end.selected === SelectType.SUB) { + this.selected = SelectType.SUB; + } else { + this.selected = SelectType.NONE; + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts new file mode 100644 index 0000000000..db14024077 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts @@ -0,0 +1,119 @@ +export class TreeListItem { + public readonly data: T; + public readonly parent: TreeListItem | null; + public readonly lvl: number; + public readonly children: TreeListItem[]; + + public collapsed: boolean; + public expandable: boolean; + public selected: boolean; + public hidden: boolean; + + 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; + } + + 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; + } + + 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; + } + } +} + + +export class TreeListData { + public readonly list: TreeListItem[]; + + private _items: TreeListItem[]; + + public get items(): TreeListItem[] { + return this._items; + } + + constructor(list: TreeListItem[]) { + this.list = list; + this._items = list; + this.updateHidden(); + } + + public select(item: TreeListItem, selected: boolean) { + item.select(selected) + } + + public collapse(item: TreeListItem, collapsed: boolean) { + item.collapse(collapsed); + this.updateHidden(); + } + + public getSelected(): TreeListItem[] { + return this.list.filter((i) => i.selected); + } + + public updateHidden() { + for (const item of this.list) { + item.updateHidden(); + } + this._items = this.list.filter((i) => !i.hidden); + } + + public static fromObject(object: any, field: string): TreeListData { + const list: TreeListItem[] = []; + const getItem = ( + data: any, + key: string, + parent: TreeListItem | null, + lvl: number + ): TreeListItem[] => { + const children: TreeListItem[] = [] + const array: any[] = data[key]; + if (Array.isArray(array)) { + for (const f of array) { + const i = new TreeListItem(f, parent, lvl); + list.push(i); + children.push(i); + const c = getItem(f, field, i, lvl + 1); + i.setChildren(c); + } + } + return children; + } + getItem(object, field, null, 0) + return new TreeListData(list); + } +} diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts new file mode 100644 index 0000000000..604331bccc --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts @@ -0,0 +1,101 @@ +import { GenerateUUIDv4 } from "@guardian/interfaces"; +import { SelectType } from "./tree-types"; +import { Line } from './tree-line'; + + +export class TreeNode { + public readonly uuid: string; + + public id: string; + public type: 'root' | 'sub'; + public childIds: Set; + public children: TreeNode[]; + public size: number; + public data: any; + public row: number; + public column: number; + public minColumn: number; + public maxColumn: number; + public minRow: number; + public maxRow: number; + public lines: Line[]; + public selected: SelectType; + public parent: TreeNode | null; + + constructor(id?: string) { + this.uuid = GenerateUUIDv4(); + + this.id = id || this.uuid; + this.type = 'root'; + this.childIds = new Set(); + this.children = []; + this.size = 1; + this.data = null; + this.row = 0; + this.column = 0; + this.minColumn = 0; + this.maxColumn = 0; + this.minRow = 0; + this.maxRow = 0; + this.lines = []; + this.selected = SelectType.NONE; + this.parent = null; + } + + public addId(id: string): void { + this.childIds.add(id); + } + + public addNode(node: TreeNode): void { + node.parent = this; + this.children.push(node); + } + + public clone(): TreeNode { + const clone = new TreeNode(this.id); + clone.type = this.type; + clone.data = this.data; + clone.childIds = new Set(this.childIds); + return clone; + } + + public resize(): void { + let size = 0; + for (const child of this.children) { + child.resize(); + size += child.size; + } + this.size = Math.max(1, size); + // if (this.size % 2 === 0) { + // this.size += 1; + // } + } + + public select(type: SelectType) { + this._select(type, true, true); + } + + private _select( + type: SelectType, + parent: boolean, + children: boolean + ) { + this.selected = type; + if (parent && this.parent) { + this.parent._select(SelectType.SUB, true, false); + } + if (children && this.children) { + for (const child of this.children) { + child._select(SelectType.SUB, false, true); + } + } + } + + public getRoot(): TreeNode { + if (this.parent) { + return this.parent.getRoot(); + } else { + return this; + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-types.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-types.ts new file mode 100644 index 0000000000..6ec8002927 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-types.ts @@ -0,0 +1,7 @@ + +export enum SelectType { + NONE = 'none', + SELECTED = 'selected', + SUB = 'sub', + HIDDEN = 'hidden' +} diff --git a/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss index 02f01e7c37..03d5c55458 100644 --- a/frontend/src/app/themes/guardian/index.scss +++ b/frontend/src/app/themes/guardian/index.scss @@ -9,4 +9,5 @@ @import 'page.scss'; @import 'input.scss'; @import 'dropdown.scss'; + @import 'tabs.scss'; } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/input.scss b/frontend/src/app/themes/guardian/input.scss index 9b4d6f1dca..edf610b711 100644 --- a/frontend/src/app/themes/guardian/input.scss +++ b/frontend/src/app/themes/guardian/input.scss @@ -29,7 +29,30 @@ } } - .p-dropdown { + .p-dropdown { width: 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/frontend/src/app/themes/guardian/tabs.scss b/frontend/src/app/themes/guardian/tabs.scss new file mode 100644 index 0000000000..e5146b36cd --- /dev/null +++ b/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/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index f8fe51ed5a..45272ebf26 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -112,18 +112,15 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Item does not exist.'); } const policyId = item.policyId; - console.log('1') const policy = await DatabaseServer.getPolicyById(policyId); - console.log('2') if (!policy || policy.status !== PolicyType.PUBLISH) { return new MessageError('Item does not exist.'); } - console.log('3') - const { schemas } = await PolicyImportExport.loadPolicyComponents(policy); - console.log('4') + const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); + const all = [].concat(schemas, toolSchemas).filter((s) => s.status === SchemaStatus.PUBLISHED) return new MessageResponse({ policy, - schemas: schemas.filter((s) => s.status === SchemaStatus.PUBLISHED) + schemas: all }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); From 544d3ed2f5623c44c6a5ca93d5a4e7e50c9d0582 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 6 Sep 2024 18:18:32 +0400 Subject: [PATCH 10/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 5 +- ...licy-statistics-configuration.component.ts | 51 +++++++++++++------ .../tree-graph/tree-graph.component.ts | 20 ++++---- .../policy-statistics/tree-graph/tree-grid.ts | 8 +-- .../policy-statistics/tree-graph/tree-line.ts | 8 +-- .../policy-statistics/tree-graph/tree-node.ts | 26 ++++++---- 6 files changed, 73 insertions(+), 45 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index a09767549a..f87b531efb 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -51,7 +51,9 @@ {{node.data.name}}
- +
+ {{item.data.title}} +
Nothing selected @@ -101,6 +103,7 @@
diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index e967b0ec0a..98ba1cd285 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -8,6 +8,14 @@ import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; import { TreeNode } from '../tree-graph/tree-node'; import { TreeListData, TreeListItem } from '../tree-graph/tree-list'; +interface SchemaData { + iri: string; + name: string; + description: string; + fields: TreeListData; + selectedFields: TreeListItem[] | null +} + @Component({ selector: 'app-policy-statistics-configuration', templateUrl: './policy-statistics-configuration.component.html', @@ -28,10 +36,10 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private subscription = new Subscription(); private tree: TreeGraphComponent; - private nodes: TreeNode[]; + private nodes: TreeNode[]; - public selectedNode: TreeNode | null = null; - public rootNode: TreeNode | null = null; + public selectedNode: TreeNode | null = null; + public rootNode: TreeNode | null = null; constructor( private profileService: ProfileService, @@ -101,15 +109,17 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { for (const schema of this.schemas) { try { const item = new Schema(schema); - const node = new TreeNode(item.iri); - node.type = item.entity === 'VC' ? 'root' : 'sub'; - node.data = { - iri: item.iri, - name: item.name, - description: item.description, - fields: TreeListData.fromObject(item, 'fields'), - selectedFields: null - } + const node = new TreeNode( + item.iri, + item.entity === 'VC' ? 'root' : 'sub', + { + iri: item.iri || '', + name: item.name || '', + description: item.description || '', + fields: TreeListData.fromObject(item, 'fields'), + selectedFields: null + } + ); for (const field of item.fields) { if (field.isRef && field.type) { node.addId(field.type) @@ -137,12 +147,23 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } } - public onSelectNode(node: TreeNode | null) { + public onSelectNode(node: TreeNode | null) { this.selectedNode = node; this.rootNode = node?.getRoot() || null; } - public onCollapseField(node: TreeNode, field: TreeListItem) { - (node.data.fields as TreeListData).collapse(field, !field.collapsed); + public onCollapseField(node: TreeNode, field: TreeListItem) { + node.data.fields.collapse(field, !field.collapsed); + } + + public onSelectField(node: TreeNode, field: TreeListItem) { + setTimeout(() => { + if (node.data) { + node.data.selectedFields = node.data.fields.getSelected(); + if (node.data.selectedFields && !node.data.selectedFields.length) { + node.data.selectedFields = null; + } + } + }); } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts index a3fa86b89d..4c6d8cfd63 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts @@ -13,9 +13,9 @@ export class TreeGraphComponent implements OnInit { @ContentChild('nodeTemplate') nodeTemplate: TemplateRef; @Output('init') initEvent = new EventEmitter(); - @Output('select') selectEvent = new EventEmitter(); + @Output('select') selectEvent = new EventEmitter | null>(); - public roots: TreeNode[]; + public roots: TreeNode[]; public grid: Grid; public width: number = 200; public zoom = 1; @@ -40,13 +40,13 @@ export class TreeGraphComponent implements OnInit { } } - public setData(nodes: TreeNode[]) { + public setData(nodes: TreeNode[]) { this.roots = this.nonUniqueNodes(nodes) this.grid = Grid.createLayout(this.width, this.roots); this.grid.render(); } - public select(node: TreeNode) { + public select(node: TreeNode) { const selected = node.selected !== SelectType.SELECTED; if (selected) { for (const node of this.grid.nodes) { @@ -65,8 +65,8 @@ export class TreeGraphComponent implements OnInit { } } - private uniqueNodes(nodes: TreeNode[]): TreeNode[] { - const nodeMap = new Map(); + private uniqueNodes(nodes: TreeNode[]): TreeNode[] { + const nodeMap = new Map>(); for (const node of nodes) { nodeMap.set(node.id, node); } @@ -77,16 +77,16 @@ export class TreeGraphComponent implements OnInit { return roots; } - private nonUniqueNodes(nodes: TreeNode[]): TreeNode[] { + private nonUniqueNodes(nodes: TreeNode[]): TreeNode[] { const roots = nodes.filter((n) => n.type === 'root'); const subs = nodes.filter((n) => n.type !== 'root'); - const nodeMap = new Map(); + const nodeMap = new Map>(); for (const node of subs) { nodeMap.set(node.id, node); } - const getSubNode = (node: TreeNode): TreeNode => { + const getSubNode = (node: TreeNode): TreeNode => { for (const id of node.childIds) { const child = nodeMap.get(id); if (child) { @@ -157,7 +157,7 @@ export class TreeGraphComponent implements OnInit { this.setZoom(this.zoom, this.gridEl.nativeElement); } - public onSelectNode(node: TreeNode) { + public onSelectNode(node: TreeNode) { this.select(node); if(node.selected === SelectType.SELECTED) { this.selectEvent.emit(node); diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts index 7a320ec91a..d33260b32a 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts @@ -5,7 +5,7 @@ import { TreeNode } from "./tree-node"; export class Grid { public column: number; public row: number; - public nodes: TreeNode[]; + public nodes: TreeNode[]; public columnsTemplate: string; public rowsTemplate: string; public width: number; @@ -23,15 +23,15 @@ export class Grid { this.width = width; } - public addNode(node: TreeNode): void { + public addNode(node: TreeNode): void { this.row = Math.max(this.row, node.row); this.column = Math.max(this.column, node.column); this.nodes.push(node); } - public static createLayout(width: number, roots: TreeNode[]): Grid { + public static createLayout(width: number, roots: TreeNode[]): Grid { const grid = new Grid(width); - const updateCoord = (node: TreeNode, row: number, column: number): number => { + const updateCoord = (node: TreeNode, row: number, column: number): number => { const max = column + node.size; node.column = column + (node.size / 2); node.minColumn = column + 1; diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts index 3d09232382..ca8eea1605 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts @@ -11,8 +11,8 @@ export class Line { y1: number; y2: number; }[]; - public start: TreeNode; - public end: TreeNode; + public start: TreeNode; + public end: TreeNode; public type: string; public selected: SelectType; private _width: number; @@ -24,11 +24,11 @@ export class Line { this.height = 0; } - public addStart(node: TreeNode): void { + public addStart(node: TreeNode): void { this.start = node; } - public addEnd(node: TreeNode): void { + public addEnd(node: TreeNode): void { this.end = node; } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts index 604331bccc..0fecdeba2f 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts @@ -3,15 +3,15 @@ import { SelectType } from "./tree-types"; import { Line } from './tree-line'; -export class TreeNode { +export class TreeNode { public readonly uuid: string; public id: string; public type: 'root' | 'sub'; public childIds: Set; - public children: TreeNode[]; + public children: TreeNode[]; public size: number; - public data: any; + public data: T; public row: number; public column: number; public minColumn: number; @@ -20,17 +20,21 @@ export class TreeNode { public maxRow: number; public lines: Line[]; public selected: SelectType; - public parent: TreeNode | null; + public parent: TreeNode | null; - constructor(id?: string) { + constructor( + id: string | null | undefined, + type: 'root' | 'sub' | null | undefined, + data: T, + ) { this.uuid = GenerateUUIDv4(); this.id = id || this.uuid; - this.type = 'root'; + this.type = type || 'root'; this.childIds = new Set(); this.children = []; this.size = 1; - this.data = null; + this.data = data; this.row = 0; this.column = 0; this.minColumn = 0; @@ -46,13 +50,13 @@ export class TreeNode { this.childIds.add(id); } - public addNode(node: TreeNode): void { + public addNode(node: TreeNode): void { node.parent = this; this.children.push(node); } - public clone(): TreeNode { - const clone = new TreeNode(this.id); + public clone(): TreeNode { + const clone = new TreeNode(this.id, this.type, this.data); clone.type = this.type; clone.data = this.data; clone.childIds = new Set(this.childIds); @@ -91,7 +95,7 @@ export class TreeNode { } } - public getRoot(): TreeNode { + public getRoot(): TreeNode { if (this.parent) { return this.parent.getRoot(); } else { From 16085eb1b6d86859ddfa829d66d90bb383bfd120 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 9 Sep 2024 18:02:08 +0400 Subject: [PATCH 11/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 44 +++-- ...cy-statistics-configuration.component.scss | 42 ++-- ...licy-statistics-configuration.component.ts | 124 +++++++++--- .../tree-graph/tree-graph.component.ts | 9 +- .../policy-statistics/tree-graph/tree-list.ts | 187 +++++++++++++++++- .../policy-statistics/tree-graph/tree-node.ts | 13 +- .../src/app/themes/guardian/checkbox.scss | 38 ++++ .../src/app/themes/guardian/collapse.scss | 44 +++++ frontend/src/app/themes/guardian/index.scss | 2 + frontend/src/app/themes/guardian/page.scss | 4 + 10 files changed, 421 insertions(+), 86 deletions(-) create mode 100644 frontend/src/app/themes/guardian/checkbox.scss create mode 100644 frontend/src/app/themes/guardian/collapse.scss diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index f87b531efb..c821e1d740 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -50,13 +50,18 @@
{{node.data.name}}
-
-
- {{item.data.title}} +
+
+ {{item.data.description}} +
+
+ + {{node.fields.selectedCount-4}} more
-
- Nothing selected +
+
+ Nothing selected +
@@ -68,6 +73,9 @@
+
+
+
{{rootNode.data.name}} @@ -82,32 +90,32 @@
-
-
+
+
- -
-
+
-
- {{item.data.title}} +
+ {{item.data.description}}
diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index 6682a792bb..75e6058bf5 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -172,13 +172,6 @@ min-width: 24px; height: 24px; overflow: hidden; - - .collapse-btn { - width: 20px; - height: 20px; - margin: 2px; - cursor: pointer; - } } .field-select { @@ -190,6 +183,11 @@ .field-name { padding-left: 8px; + + &[highlighted="true"] { + color: var(--guardian-primary-color, #4169E2); + font-weight: 600; + } } } } @@ -262,30 +260,30 @@ .node-fields { border-top: 1px solid #E1E7EF; - color: #23252E; font-family: Inter; font-size: 12px; font-weight: 500; line-height: 14px; text-align: left; - padding: 9px 8px 9px 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .node-not-fields { - border-top: 1px solid #E1E7EF; - color: #848FA9; - font-family: Inter; - font-size: 12px; - font-weight: 500; - line-height: 14px; - text-align: left; - padding: 9px 8px 9px 16px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .node-field { + color: #23252E; + border-bottom: 1px solid #E1E7EF; + padding: 8px 8px 8px 16px; + overflow: hidden; + text-overflow: ellipsis; + } + + .node-not-fields { + color: #848FA9; + border-bottom: 1px solid #E1E7EF; + padding: 8px 8px 8px 16px; + overflow: hidden; + text-overflow: ellipsis; + } } } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index 98ba1cd285..50888ae14d 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Schema, UserPermissions } from '@guardian/interfaces'; import { forkJoin, Subscription } from 'rxjs'; @@ -6,14 +6,56 @@ import { PolicyStatisticsService } from 'src/app/services/policy-statistics.serv import { ProfileService } from 'src/app/services/profile.service'; import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; import { TreeNode } from '../tree-graph/tree-node'; -import { TreeListData, TreeListItem } from '../tree-graph/tree-list'; +import { TreeListData, TreeListItem, TreeListView } from '../tree-graph/tree-list'; interface SchemaData { iri: string; name: string; description: string; - fields: TreeListData; - selectedFields: TreeListItem[] | null +} + +class SchemaNode extends TreeNode { + public fields: TreeListView; + + public override clone(): SchemaNode { + const clone = new SchemaNode(this.id, this.type, this.data); + clone.type = this.type; + clone.data = this.data; + clone.childIds = new Set(this.childIds); + clone.fields = this.fields; + return clone; + } + + public override update() { + this.fields = this.getRootFields(); + } + + public getRootFields(): TreeListView { + if (this.parent) { + const parentFields = (this.parent as SchemaNode).getRootFields(); + return parentFields.createView((s) => { + return s.parent?.data?.type === this.data.iri; + }) + } else { + return this.fields; + } + } + + public static from(schema: Schema): SchemaNode { + const id = schema.iri; + const type = schema.entity === 'VC' ? 'root' : 'sub' + const data = { + iri: schema.iri || '', + name: schema.name || '', + description: schema.description || '', + } + const result = new SchemaNode(id, type, data); + const fields = TreeListData.fromObject(schema, 'fields'); + result.fields = TreeListView.createView(fields, (s) => { + return !s.parent; + }) + return result; + } } @Component({ @@ -36,10 +78,17 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private subscription = new Subscription(); private tree: TreeGraphComponent; - private nodes: TreeNode[]; + private nodes: SchemaNode[]; - public selectedNode: TreeNode | null = null; - public rootNode: TreeNode | null = null; + public selectedNode: SchemaNode | null = null; + public rootNode: SchemaNode | null = null; + + public nodeLoading: boolean = true; + + @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; + + private _timeout1: any; + private _timeout2: any; constructor( private profileService: ProfileService, @@ -109,17 +158,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { for (const schema of this.schemas) { try { const item = new Schema(schema); - const node = new TreeNode( - item.iri, - item.entity === 'VC' ? 'root' : 'sub', - { - iri: item.iri || '', - name: item.name || '', - description: item.description || '', - fields: TreeListData.fromObject(item, 'fields'), - selectedFields: null - } - ); + const node = SchemaNode.from(item); for (const field of item.fields) { if (field.isRef && field.type) { node.addId(field.type) @@ -148,22 +187,45 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } public onSelectNode(node: TreeNode | null) { - this.selectedNode = node; - this.rootNode = node?.getRoot() || null; + clearTimeout(this._timeout1); + clearTimeout(this._timeout2); + this.nodeLoading = true; + this.selectedNode = node as SchemaNode; + this.rootNode = (node?.getRoot() || null) as SchemaNode; + if (this.rootNode) { + const id = this.selectedNode?.data?.iri; + const data = this.rootNode.fields; + data.collapseAll(true); + data.highlightAll(false); + const items = data.find((item: any) => { + return item.type === id; + }); + for (const item of items) { + data.collapsePath(item, false); + data.highlight(item, true); + } + data.update(); + this._timeout1 = setTimeout(() => { + const first = (document as any).querySelector('.field-name[highlighted="true"]'); + if (this.fieldTree && first) { + this.fieldTree.nativeElement.scrollTop = first.offsetTop; + } + this._timeout2 = setTimeout(() => { + this.nodeLoading = false; + }, 200) + }, 200) + } } - public onCollapseField(node: TreeNode, field: TreeListItem) { - node.data.fields.collapse(field, !field.collapsed); + public onCollapseField(node: SchemaNode, field: TreeListItem) { + node.fields.collapse(field, !field.collapsed); + node.fields.update(); } - public onSelectField(node: TreeNode, field: TreeListItem) { - setTimeout(() => { - if (node.data) { - node.data.selectedFields = node.data.fields.getSelected(); - if (node.data.selectedFields && !node.data.selectedFields.length) { - node.data.selectedFields = null; - } - } - }); + public onSelectField(field: TreeListItem) { + field.selected = !field.selected; + if (this.rootNode) { + this.rootNode.fields.update(); + } } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts index 4c6d8cfd63..bd24d547f3 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts @@ -86,7 +86,9 @@ export class TreeGraphComponent implements OnInit { nodeMap.set(node.id, node); } + const allNodes: TreeNode[] = []; const getSubNode = (node: TreeNode): TreeNode => { + allNodes.push(node); for (const id of node.childIds) { const child = nodeMap.get(id); if (child) { @@ -99,8 +101,13 @@ export class TreeGraphComponent implements OnInit { } for (const root of roots) { getSubNode(root); + } + for (const root of roots) { root.resize(); } + for (const node of allNodes) { + node.update(); + } return roots; } @@ -159,7 +166,7 @@ export class TreeGraphComponent implements OnInit { public onSelectNode(node: TreeNode) { this.select(node); - if(node.selected === SelectType.SELECTED) { + if (node.selected === SelectType.SELECTED) { this.selectEvent.emit(node); } else { this.selectEvent.emit(null); diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts index db14024077..573187fe93 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts @@ -8,6 +8,7 @@ export class TreeListItem { public expandable: boolean; public selected: boolean; public hidden: boolean; + public highlighted: boolean; constructor(data: T, parent: TreeListItem | null, lvl: number) { this.data = data; @@ -18,6 +19,7 @@ export class TreeListItem { this.collapsed = true; this.expandable = false; this.selected = false; + this.highlighted = false; } public setChildren(children: TreeListItem[]) { @@ -36,6 +38,17 @@ export class TreeListItem { 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; @@ -56,7 +69,6 @@ export class TreeListItem { } } - export class TreeListData { public readonly list: TreeListItem[]; @@ -68,28 +80,70 @@ export class TreeListData { constructor(list: TreeListItem[]) { this.list = list; - this._items = list; - this.updateHidden(); + this.update(); } public select(item: TreeListItem, selected: boolean) { item.select(selected) } - public collapse(item: TreeListItem, collapsed: boolean) { - item.collapse(collapsed); - this.updateHidden(); + 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 getSelected(): TreeListItem[] { - return this.list.filter((i) => i.selected); + public collapsePath(item: TreeListItem | null, collapsed: boolean) { + if (item) { + item.collapsePath(collapsed); + } } - public updateHidden() { + 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 update() { + const list = this.list; + + //Hidden + for (const item of list) { item.updateHidden(); } - this._items = this.list.filter((i) => !i.hidden); + this._items = list.filter((i) => !i.hidden); } public static fromObject(object: any, field: string): TreeListData { @@ -117,3 +171,116 @@ export class TreeListData { return new TreeListData(list); } } + +export class TreeListView { + private readonly _data: TreeListData; + private readonly _indexes: Set; + + private _selectedFields: TreeListItem[] = []; + private _selectedCount = 0; + private _selectedLimit = 0; + private _views: TreeListView[]; + + public get selectedFields(): TreeListItem[] { + return this._selectedFields; + } + + public get selectedCount(): number { + return this._selectedCount; + } + + public get items(): TreeListItem[] { + return this._data.items; + } + + constructor(data: TreeListData, indexes?: number[]) { + this._data = data; + this._views = []; + 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.update(); + } + + 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 update(updateData: boolean = true) { + if (updateData) { + this._data.update(); + } + const list = this._data.list; + 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.update(false); + } + } + + 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/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts index 0fecdeba2f..9805e16ce2 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-node.ts @@ -51,7 +51,7 @@ export class TreeNode { } public addNode(node: TreeNode): void { - node.parent = this; + node.setParent(this); this.children.push(node); } @@ -70,15 +70,16 @@ export class TreeNode { size += child.size; } this.size = Math.max(1, size); - // if (this.size % 2 === 0) { - // this.size += 1; - // } } public select(type: SelectType) { this._select(type, true, true); } + public setParent(parent: TreeNode): void { + this.parent = parent; + } + private _select( type: SelectType, parent: boolean, @@ -102,4 +103,8 @@ export class TreeNode { return this; } } + + public update(): void { + + } } diff --git a/frontend/src/app/themes/guardian/checkbox.scss b/frontend/src/app/themes/guardian/checkbox.scss new file mode 100644 index 0000000000..1b62185f38 --- /dev/null +++ b/frontend/src/app/themes/guardian/checkbox.scss @@ -0,0 +1,38 @@ +.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); + } + } + } + + &.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; + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/collapse.scss b/frontend/src/app/themes/guardian/collapse.scss new file mode 100644 index 0000000000..8308c77f7f --- /dev/null +++ b/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/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss index 03d5c55458..9aa6e9e268 100644 --- a/frontend/src/app/themes/guardian/index.scss +++ b/frontend/src/app/themes/guardian/index.scss @@ -10,4 +10,6 @@ @import 'input.scss'; @import 'dropdown.scss'; @import 'tabs.scss'; + @import 'checkbox.scss'; + @import 'collapse.scss'; } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/page.scss b/frontend/src/app/themes/guardian/page.scss index fddc92dd5e..e14eacd51e 100644 --- a/frontend/src/app/themes/guardian/page.scss +++ b/frontend/src/app/themes/guardian/page.scss @@ -6,6 +6,10 @@ position: relative; padding: 56px 48px 48px 48px; + .guardian-loading { + border-radius: 0px; + } + .guardian-user-not-registered { position: absolute; left: 50%; From ca8fb4ae0ffd16c25edfa0b2f8f54b2e43c8974a Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 11 Sep 2024 14:53:24 +0400 Subject: [PATCH 12/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 51 +++++- ...cy-statistics-configuration.component.scss | 170 +++++++++++++++++- ...licy-statistics-configuration.component.ts | 159 +++++++++------- .../schema-node.ts | 56 ++++++ .../tree-graph/tree-graph.component.html | 3 +- .../tree-graph/tree-graph.component.ts | 66 +++++-- .../policy-statistics/tree-graph/tree-grid.ts | 11 +- .../policy-statistics/tree-graph/tree-list.ts | 124 ++++++++++++- 8 files changed, 531 insertions(+), 109 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index c821e1d740..e7ddf2eb66 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -40,13 +40,17 @@
-
+
{{node.data.name}}
@@ -69,7 +73,26 @@
+
+
+
+
+
+
+
+ {{root.data.name}} +
+
@@ -79,6 +102,11 @@
{{rootNode.data.name}} +
+ +
@@ -86,12 +114,24 @@
-
+
@@ -100,7 +140,7 @@ *ngIf="item.expandable" class="guardian-collapse collapse-24" [attr.collapsed]="item.collapsed" - (click)="onCollapseField(rootNode, item)" + (click)="onCollapseField(item)" >
@@ -113,7 +153,6 @@
{{item.data.description}}
diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index 75e6058bf5..09516194ba 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -1,6 +1,7 @@ .guardian-page { position: relative; padding: 0px; + user-select: none; .header-container { padding: 56px 48px 10px 48px; @@ -48,6 +49,7 @@ .body-container { width: 100%; height: 100%; + user-select: none; .schema-viewer { position: absolute; @@ -60,7 +62,7 @@ .tree-container { position: absolute; left: 0px; - top: 40px; + top: 39px; bottom: 0px; right: 0px; z-index: 1; @@ -69,11 +71,11 @@ .schema-search { position: absolute; left: 0px; - top: 0px; + top: 40px; right: 0px; height: 56px; z-index: 2; - background: #eff3f7; + background: transparent; padding: 8px 416px 8px 16px; .guardian-search { @@ -81,6 +83,114 @@ } } + .schema-toolbar { + position: absolute; + left: 0px; + top: 0px; + right: 0px; + height: 39px; + z-index: 2; + background: #eff3f7; + padding: 8px 416px 0px 16px; + display: flex; + flex-direction: row; + border-bottom: 1px solid #E1E7EF; + user-select: none; + + .tree-tabs { + display: flex; + flex-direction: row; + overflow: hidden; + } + + .tree-tabs-nav { + display: flex; + flex-direction: row; + width: 70px; + height: 30px; + min-width: 70px; + padding: 0 5px; + + .tree-tabs-nav-left { + width: 30px; + height: 30px; + cursor: pointer; + position: relative; + + &::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; + border-top-color: transparent; + border-left-color: transparent; + border-bottom-color: transparent; + border-left-width: 0; + } + } + + .tree-tabs-nav-right { + width: 30px; + height: 30px; + cursor: pointer; + position: relative; + + &::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; + border-top-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + border-right-width: 0; + } + } + } + + .tree-tab { + max-width: 100px; + min-width: 100px; + padding: 8px 8px 8px 16px; + background: #F9FAFC; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: #23252E; + border: 1px solid #E1E7EF; + border-radius: 6px; + border-bottom-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + height: 30px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + margin-right: 4px; + } + + .tree-tab-last { + max-width: 200px; + min-width: 200px; + height: 30px; + } + } + .schema-fields { position: absolute; top: 0px; @@ -101,6 +211,19 @@ width: 0px; } + .schema-close { + width: 32px; + height: 32px; + min-width: 32px; + max-width: 32px; + position: absolute; + padding: 4px; + border-radius: 100%; + cursor: pointer; + top: 17px; + right: 16px; + } + .fields-container { width: 400px; height: 100%; @@ -115,6 +238,7 @@ line-height: 18px; text-align: left; color: #23252E; + position: relative; } .schema-config { @@ -183,11 +307,30 @@ .field-name { padding-left: 8px; + min-width: 125px; + } + + &[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; + } - &[highlighted="true"] { - color: var(--guardian-primary-color, #4169E2); - font-weight: 600; - } + &[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; } } } @@ -199,7 +342,6 @@ } } - .guardian-dropdown { &::ng-deep .p-dropdown { width: 250px; @@ -235,7 +377,7 @@ &.selected-type-selected { border: 1px solid var(--guardian-primary-color, #4169E2); - box-shadow: 0px 0px 0px 3px #4169E2, 0px 6px 6px 0px #00000021; + box-shadow: 0px 0px 0px 3px var(--guardian-primary-color, #4169E2), 0px 6px 6px 0px #00000021; } &.selected-type-sub { @@ -246,6 +388,16 @@ 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; diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index 50888ae14d..ad2bf668c0 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -6,57 +6,8 @@ import { PolicyStatisticsService } from 'src/app/services/policy-statistics.serv import { ProfileService } from 'src/app/services/profile.service'; import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; import { TreeNode } from '../tree-graph/tree-node'; -import { TreeListData, TreeListItem, TreeListView } from '../tree-graph/tree-list'; - -interface SchemaData { - iri: string; - name: string; - description: string; -} - -class SchemaNode extends TreeNode { - public fields: TreeListView; - - public override clone(): SchemaNode { - const clone = new SchemaNode(this.id, this.type, this.data); - clone.type = this.type; - clone.data = this.data; - clone.childIds = new Set(this.childIds); - clone.fields = this.fields; - return clone; - } - - public override update() { - this.fields = this.getRootFields(); - } - - public getRootFields(): TreeListView { - if (this.parent) { - const parentFields = (this.parent as SchemaNode).getRootFields(); - return parentFields.createView((s) => { - return s.parent?.data?.type === this.data.iri; - }) - } else { - return this.fields; - } - } - - public static from(schema: Schema): SchemaNode { - const id = schema.iri; - const type = schema.entity === 'VC' ? 'root' : 'sub' - const data = { - iri: schema.iri || '', - name: schema.name || '', - description: schema.description || '', - } - const result = new SchemaNode(id, type, data); - const fields = TreeListData.fromObject(schema, 'fields'); - result.fields = TreeListView.createView(fields, (s) => { - return !s.parent; - }) - return result; - } -} +import { TreeListItem } from '../tree-graph/tree-list'; +import { SchemaData, SchemaNode } from './schema-node'; @Component({ selector: 'app-policy-statistics-configuration', @@ -80,12 +31,16 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private tree: TreeGraphComponent; private nodes: SchemaNode[]; + public roots: SchemaNode[]; public selectedNode: SchemaNode | null = null; public rootNode: SchemaNode | null = null; public nodeLoading: boolean = true; + public searchField: string = ''; + @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; + @ViewChild('treeTabs', { static: false }) treeTabs: ElementRef; private _timeout1: any; private _timeout2: any; @@ -171,7 +126,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } if (this.tree) { - this.tree.setData(this.nodes) + this.tree.setData(this.nodes); } } @@ -182,10 +137,20 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public initTree($event: TreeGraphComponent) { this.tree = $event; if (this.nodes) { - this.tree.setData(this.nodes) + this.tree.setData(this.nodes); } } + public createNodes($event: any) { + const roots = $event.roots as SchemaNode[]; + const nodes = $event.nodes as SchemaNode[]; + this.roots = roots; + for (const node of nodes) { + node.fields.updateSearch(); + } + this.tree.move(18, 56); + } + public onSelectNode(node: TreeNode | null) { clearTimeout(this._timeout1); clearTimeout(this._timeout2); @@ -194,21 +159,28 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { this.rootNode = (node?.getRoot() || null) as SchemaNode; if (this.rootNode) { const id = this.selectedNode?.data?.iri; - const data = this.rootNode.fields; - data.collapseAll(true); - data.highlightAll(false); - const items = data.find((item: any) => { + const rootView = this.rootNode.fields; + rootView.collapseAll(true); + rootView.highlightAll(false); + const items = rootView.find((item: any) => { return item.type === id; }); for (const item of items) { - data.collapsePath(item, false); - data.highlight(item, true); + rootView.collapsePath(item, false); + rootView.highlight(item, true); } - data.update(); + rootView.searchItems(this.searchField); + rootView.updateHidden(); + rootView.updateSelected(); this._timeout1 = setTimeout(() => { - const first = (document as any).querySelector('.field-name[highlighted="true"]'); - if (this.fieldTree && first) { - this.fieldTree.nativeElement.scrollTop = first.offsetTop; + const first = (document as any) + .querySelector('.field-item[highlighted="true"]:not([search-highlighted="hidden"])'); + if (this.fieldTree) { + if (first) { + this.fieldTree.nativeElement.scrollTop = first.offsetTop; + } else { + this.fieldTree.nativeElement.scrollTop = 0; + } } this._timeout2 = setTimeout(() => { this.nodeLoading = false; @@ -217,15 +189,68 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } } - public onCollapseField(node: SchemaNode, field: TreeListItem) { - node.fields.collapse(field, !field.collapsed); - node.fields.update(); + public onCollapseField(field: TreeListItem) { + if (this.rootNode) { + const rootView = this.rootNode.fields; + rootView.collapse(field, !field.collapsed); + rootView.updateHidden(); + } } public onSelectField(field: TreeListItem) { field.selected = !field.selected; if (this.rootNode) { - this.rootNode.fields.update(); + const rootView = this.rootNode.fields; + rootView.updateHidden(); + rootView.updateSelected(); + } + } + + public onSchemaFilter($event: any) { + const value = ($event.target.value || '').trim().toLocaleLowerCase(); + if (this.tree) { + const roots = this.tree.getRoots() as SchemaNode[]; + for (const root of roots) { + root.fields.searchItems(value); + } + + const nodes = this.tree.getNodes() as SchemaNode[]; + for (const node of nodes) { + node.fields.searchView(value); + } + + if (this.rootNode) { + const rootView = this.rootNode.fields; + rootView.updateHidden(); + } } } + + public onNavRoot(root: SchemaNode) { + const el = document.querySelector(`.tree-node[node-id="${root.uuid}"]`); + const grid = el?.parentElement?.parentElement; + if (el && grid) { + const elCoord = el.getBoundingClientRect(); + const gridCoord = grid.getBoundingClientRect(); + const x = elCoord.left - gridCoord.left; + this.tree?.move(-x + 50, 56) + } + } + + public onNavNext(dir: number) { + const el = this.treeTabs.nativeElement; + const max = Math.floor((el.scrollWidth - el.offsetWidth) / 104); + let current = Math.floor(this.treeTabs.nativeElement.scrollLeft / 104); + if (dir < 0) { + current--; + } else { + current++; + } + current = Math.min(Math.max(current, 0), max); + this.treeTabs.nativeElement.scrollLeft = current * 104; + } + + public onClearNode() { + this.tree?.onSelectNode(null); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts new file mode 100644 index 0000000000..bf443fc4fe --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts @@ -0,0 +1,56 @@ +import { Schema } from "@guardian/interfaces"; +import { TreeListView, TreeListData } from "../tree-graph/tree-list"; +import { TreeNode } from "../tree-graph/tree-node"; + +export interface SchemaData { + iri: string; + name: string; + description: string; +} + +export class SchemaNode extends TreeNode { + public fields: TreeListView; + + public override clone(): SchemaNode { + const clone = new SchemaNode(this.id, this.type, this.data); + clone.type = this.type; + clone.data = this.data; + clone.childIds = new Set(this.childIds); + clone.fields = this.fields; + return clone; + } + + public override update() { + this.fields = this.getRootFields(); + } + + public getRootFields(): TreeListView { + if (this.parent) { + const parentFields = (this.parent as SchemaNode).getRootFields(); + return parentFields.createView((s) => { + return s.parent?.data?.type === this.data.iri; + }); + } else { + return this.fields; + } + } + + public static from(schema: Schema): SchemaNode { + const id = schema.iri; + const type = schema.entity === 'VC' ? 'root' : 'sub'; + const data = { + iri: schema.iri || '', + name: schema.name || '', + description: schema.description || '', + }; + const result = new SchemaNode(id, type, data); + const fields = TreeListData.fromObject(schema, 'fields'); + result.fields = TreeListView.createView(fields, (s) => { + return !s.parent; + }); + result.fields.setSearchRules((item) => { + return `(${item.description || ''})`.toLocaleLowerCase(); + }) + return result; + } +} diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html index c2e5ec7313..8887f26ea3 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html @@ -6,6 +6,7 @@ (mouseup)="onMouseUp($event)" (mousemove)="onMouseMove($event)" (mouseleave)="onMouseUp($event)" + (mouseenter)="onMouseUp($event)" (wheel)="onScroll($event)" >
-
+
(); @Output('select') selectEvent = new EventEmitter | null>(); + @Output('render') renderEvent = new EventEmitter(); public roots: TreeNode[]; + public nodes: TreeNode[]; public grid: Grid; public width: number = 200; public zoom = 1; + public toolbar = true; constructor() { @@ -41,14 +44,29 @@ export class TreeGraphComponent implements OnInit { } public setData(nodes: TreeNode[]) { - this.roots = this.nonUniqueNodes(nodes) + const { roots, allNodes } = this.nonUniqueNodes(nodes); + this.roots = roots; + this.nodes = allNodes; this.grid = Grid.createLayout(this.width, this.roots); this.grid.render(); + this.renderEvent.emit({ + grid: this.grid, + roots: this.roots, + nodes: this.nodes + }) } - public select(node: TreeNode) { - const selected = node.selected !== SelectType.SELECTED; - if (selected) { + public getNodes(): TreeNode[] { + return this.nodes; + } + + public getRoots(): TreeNode[] { + return this.roots; + } + + public select(node: TreeNode | null) { + const selected = node && node.selected !== SelectType.SELECTED; + if (node && selected) { for (const node of this.grid.nodes) { node.selected = SelectType.HIDDEN; } @@ -77,7 +95,7 @@ export class TreeGraphComponent implements OnInit { return roots; } - private nonUniqueNodes(nodes: TreeNode[]): TreeNode[] { + private nonUniqueNodes(nodes: TreeNode[]) { const roots = nodes.filter((n) => n.type === 'root'); const subs = nodes.filter((n) => n.type !== 'root'); @@ -108,7 +126,7 @@ export class TreeGraphComponent implements OnInit { for (const node of allNodes) { node.update(); } - return roots; + return { roots, allNodes }; } public setZoom(zoom: number, el: any) { @@ -137,21 +155,31 @@ export class TreeGraphComponent implements OnInit { } public onMouseDown($event: any) { + if ($event.stopPropagation) { + $event.stopPropagation() + } this.grid.onMove(true, $event); - this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; - this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + this.gridEl.nativeElement.style.left = `${this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${this.grid.y}px`; } public onMouseUp($event: any) { + if ($event.stopPropagation) { + $event.stopPropagation() + } this.grid.onMove(false, $event); - this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; - this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + this.gridEl.nativeElement.style.left = `${this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${this.grid.y}px`; } public onMouseMove($event: any) { - this.grid.onMoving($event); - this.gridEl.nativeElement.style.left = `${-this.grid.x}px`; - this.gridEl.nativeElement.style.top = `${-this.grid.y}px`; + if ($event.stopPropagation) { + $event.stopPropagation() + } + if (this.grid.onMoving($event)) { + this.gridEl.nativeElement.style.left = `${this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${this.grid.y}px`; + } } public onScroll($event: any) { @@ -164,12 +192,20 @@ export class TreeGraphComponent implements OnInit { this.setZoom(this.zoom, this.gridEl.nativeElement); } - public onSelectNode(node: TreeNode) { + public onSelectNode(node: TreeNode | null) { this.select(node); - if (node.selected === SelectType.SELECTED) { + if (node && node.selected === SelectType.SELECTED) { this.selectEvent.emit(node); } else { this.selectEvent.emit(null); } } + + public move(x: number, y: number): void { + if (this.grid) { + this.grid.move(x, y); + this.gridEl.nativeElement.style.left = `${this.grid.x}px`; + this.gridEl.nativeElement.style.top = `${this.grid.y}px`; + } + } } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts index d33260b32a..c7bd850666 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-grid.ts @@ -86,10 +86,17 @@ export class Grid { public onMoving($event: any) { if (this.moving) { - this.x = this.x + (this._clientX - $event.clientX); - this.y = this.y + (this._clientY - $event.clientY); + this.x = this.x - (this._clientX - $event.clientX); + this.y = this.y - (this._clientY - $event.clientY); this._clientX = $event.clientX; this._clientY = $event.clientY; + return true; } + return false; + } + + public move(x: number, y: number): void { + this.x = x; + this.y = y; } } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts index 573187fe93..1c309af6c3 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts @@ -9,6 +9,9 @@ export class TreeListItem { 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; @@ -20,6 +23,9 @@ export class TreeListItem { this.expandable = false; this.selected = false; this.highlighted = false; + this.search = ''; + this.searchChildren = ''; + this.searchHighlighted = ''; } public setChildren(children: TreeListItem[]) { @@ -67,6 +73,15 @@ export class TreeListItem { this.hidden = false; } } + + public setSearchRules(f: (item: T) => string) { + this.searchHighlighted = ''; + this.search = f(this.data); + this.searchChildren = this.search + '|'; + for (const child of this.children) { + this.searchChildren = this.searchChildren + child.searchChildren + '|'; + } + } } export class TreeListData { @@ -80,7 +95,7 @@ export class TreeListData { constructor(list: TreeListItem[]) { this.list = list; - this.update(); + this.updateHidden(); } public select(item: TreeListItem, selected: boolean) { @@ -136,16 +151,42 @@ export class TreeListData { return result; } - public update() { + public updateHidden() { const list = this.list; - - //Hidden 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): void { + if (text) { + for (const item of this.list) { + if (item.search.includes(text)) { + item.searchHighlighted = 'highlighted'; + } else { + if (item.searchChildren.includes(text)) { + item.searchHighlighted = 'sub'; + item.collapsed = false; + } else { + item.searchHighlighted = 'hidden'; + } + } + } + } else { + for (const item of this.list) { + item.searchHighlighted = ''; + } + } + } + public static fromObject(object: any, field: string): TreeListData { const list: TreeListItem[] = []; const getItem = ( @@ -180,6 +221,8 @@ export class TreeListView { private _selectedCount = 0; private _selectedLimit = 0; private _views: TreeListView[]; + private _search: string; + private _searchHighlighted: boolean; public get selectedFields(): TreeListItem[] { return this._selectedFields; @@ -193,9 +236,18 @@ export class TreeListView { 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 = ''; if (indexes) { this._indexes = new Set(indexes); } else { @@ -204,7 +256,8 @@ export class TreeListView { this._indexes.add(index); } } - this.update(); + this.updateHidden(); + this.updateSelected(); } public setSelectedLimit(limit: number) { @@ -243,10 +296,63 @@ export class TreeListView { return this._data.find(f); } - public update(updateData: boolean = true) { - if (updateData) { - this._data.update(); + public filterOne(text: string): TreeListItem | null { + for (const index of this._indexes) { + const item = this._data.list[index]; + if (item.search.includes(text)) { + return item; + } + } + return null; + } + + public filter(text: string): TreeListItem[] { + const result: TreeListItem[] = [] + for (const index of this._indexes) { + const item = this._data.list[index]; + if (item.search.includes(text)) { + result.push(item); + } + } + return result; + } + + 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 + '|'; + } } + } + + public searchItems(text: string): void { + this._data.searchItems(text); + } + + public searchView(text: string): void { + this._searchHighlighted = false; + if (text) { + for (const index of this._indexes) { + const item = this._data.list[index]; + if (item.search.includes(text)) { + this._searchHighlighted = true; + return; + } + } + } + } + + public updateHidden() { + this._data.updateHidden(); + } + + public updateSelected() { const list = this._data.list; this._selectedFields = list.filter((item, index) => item.selected && this._indexes.has(index)); this._selectedCount = this._selectedFields.length; @@ -254,7 +360,7 @@ export class TreeListView { this._selectedFields = this._selectedFields.slice(0, this._selectedLimit); } for (const view of this._views) { - view.update(false); + view.updateSelected(); } } From 06eed3b48602cbf7238aa0b9713b8d762319f27f Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 13 Sep 2024 17:38:20 +0400 Subject: [PATCH 13/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 565 ++++++++++++++---- ...cy-statistics-configuration.component.scss | 322 +++++++++- ...licy-statistics-configuration.component.ts | 150 ++++- .../schema-node.ts | 229 ++++++- .../policy-statistics.module.ts | 2 + .../tree-graph/tree-graph.component.ts | 30 +- .../policy-statistics/tree-graph/tree-list.ts | 109 ++-- frontend/src/app/services/branding.service.ts | 8 +- .../src/app/themes/guardian/checkbox.scss | 52 ++ frontend/src/app/themes/guardian/index.scss | 1 + .../src/app/themes/guardian/separator.scss | 67 +++ .../src/assets/images/icons/16/center.svg | 3 + frontend/src/assets/images/icons/16/minus.svg | 3 + .../assets/images/icons/16/right-arrow.svg | 4 + 14 files changed, 1313 insertions(+), 232 deletions(-) create mode 100644 frontend/src/app/themes/guardian/separator.scss create mode 100644 frontend/src/assets/images/icons/16/center.svg create mode 100644 frontend/src/assets/images/icons/16/minus.svg create mode 100644 frontend/src/assets/images/icons/16/right-arrow.svg diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index e7ddf2eb66..2308a77a43 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -24,7 +24,7 @@
- {{title}} + {{item?.name || title}}
Policy Name: {{policy.name}} Version: {{policy.version}} @@ -33,152 +33,469 @@
- - +
+
+
+ + +
+
+ + +
+ +
+ + +
+ +
+ + + +
+
+
-
-
- +
+ + +
- -
-
- {{node.data.name}} -
-
-
- {{item.data.description}} -
-
- + {{node.fields.selectedCount-4}} more -
-
-
-
- Nothing selected -
-
+
+ {{node.data.name}} +
+
+
+ {{item.data.description}} +
+
+ + {{node.fields.selectedCount-4}} more +
+
+
+
+ Nothing selected
- - -
- -
-
-
-
-
-
-
- {{root.data.name}}
-
+ + +
+ +
+
+
+
+
+
+
+ {{root.data.name}} + + {{root.fields.selectedAllCount}} +
-
-
-
+
+
+
+
+
+ +
+
+ +
+
+
{{zoom}}%
+
+
+ +
+
+
+
+
+
+
+
+ {{rootNode.data.name}} +
+
-
-
- {{rootNode.data.name}} -
- -
-
-
- - -
- - - +
+
+
+ Input +
+
+
+
ID
+
SCHEMA
+
PROPERTY
+
FIELD
+
TYPE
+
+
+
+
+ {{variable.id}} +
+
+ {{variable.namePath}} +
+
+ {{variable.propertyName}} +
+
+ {{variable.description}} +
+
+ {{variable.type}}
- - - - - - - +
+
+
+ Output +
+
+
+
ID
+
TYPE
+
DESCRIPTION
+
FORMULA
+
+
+
+
+
+ +
+
{{formula.type}}
+
+ +
+
+ +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
Overview
+
+
+
+
+ + +
+
Select Schemas
+
+
+
+
+ + +
+
Configure Rules
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index 09516194ba..ef782bbd2f 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -40,16 +40,46 @@ height: 64px; width: 100%; display: flex; - justify-content: flex-end; + justify-content: center; padding: 12px 48px; background: #fff; border-top: 1px solid #E1E7EF; + position: relative; + + button { + position: absolute; + height: 40px; + top: 12px; + right: 48px; + width: 135px; + } + + .guardian-step-container { + height: 40px; + width: 620px; + } } .body-container { width: 100%; height: 100%; user-select: none; + position: relative; + + .overview-viewer { + padding: 24px 48px; + + form { + box-shadow: 0px 4px 8px 0px #00000014; + background: #fff; + padding: 24px 24px 2px 24px; + border-radius: 8px; + + .guardian-input-container { + margin-bottom: 24px; + } + } + } .schema-viewer { position: absolute; @@ -80,6 +110,11 @@ .guardian-search { max-width: 500px; + + input { + height: 28px; + font-size: 12px; + } } } @@ -90,11 +125,11 @@ right: 0px; height: 39px; z-index: 2; - background: #eff3f7; - padding: 8px 416px 0px 16px; + background: #F9FAFC; + padding: 8px 16px 0px 16px; display: flex; flex-direction: row; - border-bottom: 1px solid #E1E7EF; + border-bottom: 1px solid #C4D0E1; user-select: none; .tree-tabs { @@ -102,7 +137,7 @@ flex-direction: row; overflow: hidden; } - + .tree-tabs-nav { display: flex; flex-direction: row; @@ -161,39 +196,118 @@ } .tree-tab { - max-width: 100px; - min-width: 100px; padding: 8px 8px 8px 16px; - background: #F9FAFC; + background: #FFFFFF; font-family: Inter; font-size: 12px; font-weight: 500; line-height: 14px; text-align: left; color: #23252E; - border: 1px solid #E1E7EF; + border: 1px solid #C4D0E1; border-radius: 6px; border-bottom-width: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; height: 30px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; cursor: pointer; margin-right: 4px; + display: flex; + min-width: 110px; + max-width: 110px; + + &.selected-type-selected, + &.selected-type-sub { + color: var(--guardian-primary-color, #4169E2); + border-color: var(--guardian-primary-color, #4169E2); + background: #4169E214; + } + + .tree-tab-name { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .tree-tab-count { + margin-left: 8px; + background-color: var(--guardian-primary-color, #4169E2); + width: min-content; + display: flex; + align-items: center; + justify-content: center; + padding: 0px 8px; + border-radius: 16px; + cursor: pointer; + color: #fff; + font-size: 10px; + height: 16px; + position: relative; + top: -1px; + } } .tree-tab-last { - max-width: 200px; - min-width: 200px; + max-width: 300px; + min-width: 300px; height: 30px; } } + .zoom-toolbar { + width: 48px; + height: 160px; + position: absolute; + right: 400px; + top: 39px; + 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: #fff; + box-shadow: 0px 0px 1px 3px #eff3f7; + + .zoom-label { + width: 28px; + height: 28px; + border-radius: 8px; + font-family: Inter; + font-size: 8px; + font-weight: 700; + color: #848FA9; + border: 1px solid #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; + top: 39px; bottom: 0px; right: 0px; width: 400px; @@ -308,6 +422,11 @@ .field-name { padding-left: 8px; min-width: 125px; + cursor: pointer; + } + + &[property="false"] { + opacity: 0.4; } &[highlighted="true"] .field-name { @@ -336,6 +455,177 @@ } } } + + .rules-list { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: 24px 0px; + + .schema-rule { + .schema-rule-name { + font-family: Inter; + font-size: 14px; + font-weight: 700; + line-height: 18px; + text-align: left; + color: #000000; + width: 100%; + padding-bottom: 16px; + } + + .schema-rule-values { + display: flex; + flex-direction: column; + width: 100%; + + .schema-rule-value { + display: flex; + flex-direction: row; + width: 100%; + height: 40px; + padding-bottom: 16px; + align-items: center; + cursor: pointer; + + .schema-rule-value-select { + width: 24px; + height: 24px; + } + + .schema-rule-value-name { + padding: 4px 0 4px 8px; + height: 24px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + line-height: 16px; + text-align: left; + color: #23252E; + cursor: pointer; + } + } + } + } + } + } + } + } + + .config-viewer { + padding: 32px 48px; + + .variables-container { + box-shadow: 0px 4px 8px 0px #00000014; + background: #FFFFFF; + padding: 24px; + border-radius: 8px; + margin-bottom: 16px; + + .variables-header { + color: #181818; + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + padding-bottom: 16px; + } + + .variables-grid { + .cell-48 { + min-width: 48px; + width: 48px; + max-width: 48px; + } + + .cell-64 { + min-width: 64px; + width: 64px; + max-width: 64px; + } + + .cell-84 { + min-width: 84px; + width: 84px; + max-width: 84px; + } + + .cell-200 { + min-width: 200px; + width: 200px; + max-width: 200px; + } + + .cell-300 { + min-width: 300px; + width: 300px; + max-width: 300px; + } + + .variables-grid-cell { + min-height: 40px; + padding: 6px 16px; + display: flex; + align-items: center; + width: 100%; + + input { + border: none; + outline: none; + box-shadow: none; + width: 100%; + padding: 8px 0px; + } + } + + .variables-grid-header { + display: flex; + flex-direction: row; + padding: 0px 8px; + + &>div { + color: #848FA9; + font-family: Inter; + font-size: 12px; + font-weight: 400; + text-align: left; + } + } + + .variables-grid-body { + border: 1px solid #E1E7EF; + border-radius: 8px; + + .variables-grid-row { + min-height: 40px; + display: flex; + flex-direction: row; + border-bottom: 1px solid #E1E7EF; + padding: 0px 8px; + + &>div { + border-right: 1px solid #E1E7EF; + + &:last-child { + border-right: none; + } + } + + &:last-child { + border-bottom: none; + } + } + } + } + + .variables-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } } } } @@ -344,7 +634,7 @@ .guardian-dropdown { &::ng-deep .p-dropdown { - width: 250px; + width: 100%; } } diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index ad2bf668c0..508d90a17b 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -7,7 +7,9 @@ import { ProfileService } from 'src/app/services/profile.service'; import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; import { TreeNode } from '../tree-graph/tree-node'; import { TreeListItem } from '../tree-graph/tree-list'; -import { SchemaData, SchemaNode } from './schema-node'; +import { SchemaData, SchemaFormulas, SchemaNode, SchemaVariables } from './schema-node'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { SchemaService } from 'src/app/services/schema.service'; @Component({ selector: 'app-policy-statistics-configuration', @@ -36,17 +38,61 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public rootNode: SchemaNode | null = null; public nodeLoading: boolean = true; - public searchField: string = ''; + public stepper = [true, false, false]; + @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; @ViewChild('treeTabs', { static: false }) treeTabs: ElementRef; private _timeout1: any; private _timeout2: any; + public formulas: SchemaFormulas = new SchemaFormulas(); + public variables: SchemaVariables = new SchemaVariables(); + + public overviewForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl('', Validators.required), + method: new FormControl('', Validators.required) + }); + + public schemaFilterType: number = 1; + + public methods: any[] = [{ + label: 'Manually', + value: 'manually' + }, { + label: 'By Event', + value: 'byEvent' + }, { + label: 'every Day', + value: 'everyDay' + }, { + label: 'every Week', + value: 'everyWeek' + }, { + label: 'every Month', + value: 'everyMonth' + }, { + label: 'every Year', + value: 'everyYear' + }]; + + public properties: Map; + + public get zoom(): number { + if (this.tree) { + return Math.round(this.tree.zoom * 100); + } else { + return 100; + } + } + constructor( private profileService: ProfileService, + private schemaService: SchemaService, private policyStatisticsService: PolicyStatisticsService, private router: Router, private route: ActivatedRoute @@ -93,27 +139,38 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { forkJoin([ this.policyStatisticsService.getItem(this.id), this.policyStatisticsService.getRelationships(this.id), - ]).subscribe(([item, relationships]) => { + this.schemaService.properties() + ]).subscribe(([item, relationships, properties]) => { this.item = item; if (relationships) { - this.prepareData(relationships); + this.prepareData(item, relationships, properties); } setTimeout(() => { this.loading = false; - }, 500); + }, 1000); }, (e) => { this.loading = false; }); } - private prepareData(relationships: any) { + private prepareData( + item: any, + relationships: any, + properties: any[] + ) { this.policy = relationships.policy || {}; this.schemas = relationships.schemas || []; this.nodes = []; + this.properties = new Map(); + if(properties) { + for (const property of properties) { + this.properties.set(property.title, property.value); + } + } for (const schema of this.schemas) { try { const item = new Schema(schema); - const node = SchemaNode.from(item); + const node = SchemaNode.from(item, this.properties); for (const field of item.fields) { if (field.isRef && field.type) { node.addId(field.type) @@ -128,6 +185,13 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { if (this.tree) { this.tree.setData(this.nodes); } + + this.overviewForm.setValue({ + name: item.name, + description: item.description, + policy: this.policy?.name, + method: item.description, + }); } public onBack() { @@ -148,7 +212,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { for (const node of nodes) { node.fields.updateSearch(); } - this.tree.move(18, 56); + this.tree.move(18, 46); } public onSelectNode(node: TreeNode | null) { @@ -169,7 +233,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { rootView.collapsePath(item, false); rootView.highlight(item, true); } - rootView.searchItems(this.searchField); + rootView.searchItems(this.searchField, this.schemaFilterType); rootView.updateHidden(); rootView.updateSelected(); this._timeout1 = setTimeout(() => { @@ -204,14 +268,17 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { rootView.updateHidden(); rootView.updateSelected(); } + this.updateVariables(); } - public onSchemaFilter($event: any) { - const value = ($event.target.value || '').trim().toLocaleLowerCase(); + public onSchemaFilter() { + clearTimeout(this._timeout2); + this.nodeLoading = true; + const value = (this.searchField || '').trim().toLocaleLowerCase(); if (this.tree) { const roots = this.tree.getRoots() as SchemaNode[]; for (const root of roots) { - root.fields.searchItems(value); + root.fields.searchItems(value, this.schemaFilterType); } const nodes = this.tree.getNodes() as SchemaNode[]; @@ -224,6 +291,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { rootView.updateHidden(); } } + this._timeout2 = setTimeout(() => { + this.nodeLoading = false; + }, 200) } public onNavRoot(root: SchemaNode) { @@ -233,24 +303,72 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { const elCoord = el.getBoundingClientRect(); const gridCoord = grid.getBoundingClientRect(); const x = elCoord.left - gridCoord.left; - this.tree?.move(-x + 50, 56) + this.tree?.move(-x + 50, 56); + this.tree.onSelectNode(root); } } public onNavNext(dir: number) { const el = this.treeTabs.nativeElement; - const max = Math.floor((el.scrollWidth - el.offsetWidth) / 104); - let current = Math.floor(this.treeTabs.nativeElement.scrollLeft / 104); + const max = Math.floor((el.scrollWidth - el.offsetWidth) / 114); + let current = Math.floor(this.treeTabs.nativeElement.scrollLeft / 114); if (dir < 0) { current--; } else { current++; } current = Math.min(Math.max(current, 0), max); - this.treeTabs.nativeElement.scrollLeft = current * 104; + this.treeTabs.nativeElement.scrollLeft = current * 114; } public onClearNode() { this.tree?.onSelectNode(null); } + + public onStep(index: number) { + this.loading = true; + setTimeout(() => { + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = false; + } + this.stepper[index] = true; + this.tree?.move(18, 46); + if (index === 1) { + setTimeout(() => { + this.tree?.refresh(); + this.loading = false; + }, 3000); + } else { + setTimeout(() => { + this.loading = false; + }, 800); + } + }, 300); + } + + public onZoom(d: number) { + if (this.tree) { + this.tree.onZoom(d); + if (d === 0) { + this.tree.move(18, 46); + } + } + } + + public schemaConfigChange($event: any) { + if ($event.index === 1) { + this.schemaFilterType = 2; + } else { + this.schemaFilterType = 1; + } + this.onSchemaFilter(); + } + + private updateVariables() { + this.variables.fromNodes(this.roots); + } + + public onAddVariable() { + this.formulas.add(); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts index bf443fc4fe..ef45f0c71f 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts @@ -1,5 +1,5 @@ import { Schema } from "@guardian/interfaces"; -import { TreeListView, TreeListData } from "../tree-graph/tree-list"; +import { TreeListView, TreeListData, TreeListItem } from "../tree-graph/tree-list"; import { TreeNode } from "../tree-graph/tree-node"; export interface SchemaData { @@ -8,8 +8,27 @@ export interface SchemaData { description: string; } +export interface FieldData { + name: string; + type: string; + description: string; + property: string; + propertyName: string; +} + +export class SchemaRules { + public relationships: 'main' | 'related' | 'unrelated'; + public unique: 'true' | 'false'; + + constructor() { + this.relationships = 'unrelated'; + this.unique = 'false'; + } +} + export class SchemaNode extends TreeNode { - public fields: TreeListView; + public fields: TreeListView; + public rules: SchemaRules; public override clone(): SchemaNode { const clone = new SchemaNode(this.id, this.type, this.data); @@ -17,6 +36,7 @@ export class SchemaNode extends TreeNode { clone.data = this.data; clone.childIds = new Set(this.childIds); clone.fields = this.fields; + clone.rules = this.rules; return clone; } @@ -24,7 +44,7 @@ export class SchemaNode extends TreeNode { this.fields = this.getRootFields(); } - public getRootFields(): TreeListView { + public getRootFields(): TreeListView { if (this.parent) { const parentFields = (this.parent as SchemaNode).getRootFields(); return parentFields.createView((s) => { @@ -35,22 +55,217 @@ export class SchemaNode extends TreeNode { } } - public static from(schema: Schema): SchemaNode { + public static from(schema: Schema, properties: Map): SchemaNode { const id = schema.iri; const type = schema.entity === 'VC' ? 'root' : 'sub'; const data = { iri: schema.iri || '', name: schema.name || '', - description: schema.description || '', + description: schema.description || '' }; const result = new SchemaNode(id, type, data); - const fields = TreeListData.fromObject(schema, 'fields'); + const fields = TreeListData.fromObject(schema, 'fields', (f) => { + if (f.data.property) { + f.data.propertyName = properties.get(f.data.property) || f.data.property; + } + return f; + }); result.fields = TreeListView.createView(fields, (s) => { return !s.parent; }); result.fields.setSearchRules((item) => { - return `(${item.description || ''})`.toLocaleLowerCase(); + return [ + `(${item.description || ''})|(${item.propertyName || ''})`.toLocaleLowerCase(), + `(${item.description || ''})`.toLocaleLowerCase(), + `(${item.propertyName || ''})`.toLocaleLowerCase() + ]; }) + result.rules = new SchemaRules(); return result; } } + +export class SchemaVariable { + public id: string; + public path: string; + public namePath: string; + public schemaId: string; + public schema: string; + public description: string; + public property: string; + public propertyName: string; + public type: string; + public index: number; + + constructor() { + } +} + +export class SchemaVariables { + private readonly symbol = 'A' + private startIndex: number = 1; + + public variables: SchemaVariable[]; + public names: Set; + + constructor() { + this.variables = []; + this.names = new Set(); + } + + public getName(): string { + let name: string = ''; + for (let index = this.startIndex; index < 1000000; index++) { + name = `${this.symbol}${index}`; + if (!this.names.has(name)) { + this.names.add(name); + return name; + } + } + return name; + } + + public fromData(data: any[]) { + const map = new Map(); + if (data) { + for (let index = 0; index < data.length; index++) { + const item = data[index]; + const variable = new SchemaVariable(); + variable.id = item.id; + variable.schemaId = item.schemaId; + variable.path = item.path; + variable.namePath = item.namePath; + variable.schema = item.schema; + variable.description = item.description; + variable.type = item.type; + variable.property = item.property; + variable.propertyName = item.propertyName; + variable.index = index; + + const fullPath = `${variable.schemaId}.${variable.path}`; + map.set(fullPath, variable); + } + } + + this.variables = []; + for (const item of map.values()) { + this.variables.push(item); + this.names.add(item.id); + } + this.variables.sort((a, b) => a.index > b.index ? 1 : -1); + for (let index = 0; index < this.variables.length; index++) { + this.variables[index].index = index; + } + this.startIndex = this.variables.length + 1; + } + + public fromNodes(rootNode: SchemaNode[]) { + const map = new Map(); + if (rootNode) { + let index = 1000000000; + for (const root of rootNode) { + const fields = root.fields.getSelected(); + for (const field of fields) { + index++; + const variable = new SchemaVariable(); + const path = field.getPath(); + variable.id = ''; + variable.schemaId = root.data.iri; + variable.path = path.map((e) => e.data.name).join('.'); + variable.namePath = path.map((e) => e.data.description).join(' / '); + variable.schema = root.data.name; + variable.description = field.data.description; + variable.type = field.data.type; + variable.property = field.data.property; + variable.propertyName = field.data.propertyName; + variable.index = index; + const fullPath = `${variable.schemaId}.${variable.path}`; + map.set(fullPath, variable); + } + } + } + + for (const variable of this.variables) { + const fullPath = `${variable.schemaId}.${variable.path}`; + if (map.has(fullPath)) { + map.set(fullPath, variable); + } + } + + this.variables = []; + for (const item of map.values()) { + if (item.index > 1000000000) { + item.id = this.getName(); + } + this.variables.push(item); + this.names.add(item.id); + } + + this.variables.sort((a, b) => a.index > b.index ? 1 : -1); + for (let index = 0; index < this.variables.length; index++) { + this.variables[index].index = index; + } + } +} + +export class SchemaFormula { + public id: string; + public type: string; + public description: string; + public formula: string; + public index: number; + + constructor() { + } +} + +export class SchemaFormulas { + private readonly symbol = 'B' + private startIndex: number = 1; + + public formulas: SchemaFormula[]; + public names: Set; + + constructor() { + this.formulas = []; + this.names = new Set(); + } + + public getName(): string { + let name: string = ''; + for (let index = this.startIndex; index < 1000000; index++) { + name = `${this.symbol}${index}`; + if (!this.names.has(name)) { + this.names.add(name); + return name; + } + } + return name; + } + + public add() { + const formula = new SchemaFormula(); + formula.id = this.getName(); + this.formulas.push(formula); + } + + public fromData(data: any[]) { + this.formulas = []; + if (data) { + for (let index = 0; index < data.length; index++) { + const item = data[index]; + const formula = new SchemaFormula(); + formula.id = item.id; + formula.type = item.type; + formula.description = item.description; + formula.formula = item.formula; + formula.index = index; + this.formulas.push(formula); + } + } + for (const item of this.formulas) { + this.names.add(item.id); + } + this.startIndex = this.formulas.length + 1; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index 22f6488ac5..215f078aba 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -17,6 +17,7 @@ import { PolicyStatisticsConfigurationComponent } from './policy-statistics-conf import { TreeGraphComponent } from './tree-graph/tree-graph.component'; import { TabViewModule } from 'primeng/tabview'; import { CheckboxModule } from 'primeng/checkbox'; +import { RadioButtonModule } from 'primeng/radiobutton'; @NgModule({ declarations: [ @@ -39,6 +40,7 @@ import { CheckboxModule } from 'primeng/checkbox'; DropdownModule, TabViewModule, CheckboxModule, + RadioButtonModule, AngularSvgIconModule.forRoot(), ], exports: [], diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts index 5a0fda1775..f907e8c337 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.ts @@ -145,7 +145,9 @@ export class TreeGraphComponent implements OnInit { } public onZoom(zoom: number) { - if (zoom > 0) { + if (zoom === 0) { + this.zoom = 1; + } else if (zoom > 0) { this.zoom += 0.1; } else { this.zoom -= 0.1; @@ -154,6 +156,16 @@ export class TreeGraphComponent implements OnInit { this.setZoom(this.zoom, this.gridEl.nativeElement); } + public onScroll($event: any) { + if ($event.deltaY < 0) { + this.zoom = this.zoom * 1.1; + } else { + this.zoom = this.zoom * 0.9; + } + this.zoom = Math.max(this.zoom, 0.1); + this.setZoom(this.zoom, this.gridEl.nativeElement); + } + public onMouseDown($event: any) { if ($event.stopPropagation) { $event.stopPropagation() @@ -182,16 +194,6 @@ export class TreeGraphComponent implements OnInit { } } - public onScroll($event: any) { - if ($event.deltaY < 0) { - this.zoom = this.zoom * 1.1; - } else { - this.zoom = this.zoom * 0.9; - } - this.zoom = Math.max(this.zoom, 0.1); - this.setZoom(this.zoom, this.gridEl.nativeElement); - } - public onSelectNode(node: TreeNode | null) { this.select(node); if (node && node.selected === SelectType.SELECTED) { @@ -204,6 +206,12 @@ export class TreeGraphComponent implements OnInit { public move(x: number, y: number): void { if (this.grid) { this.grid.move(x, y); + } + this.refresh(); + } + + public refresh() { + if (this.gridEl) { this.gridEl.nativeElement.style.left = `${this.grid.x}px`; this.gridEl.nativeElement.style.top = `${this.grid.y}px`; } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts index 1c309af6c3..652321f2df 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts @@ -10,8 +10,8 @@ export class TreeListItem { public hidden: boolean; public highlighted: boolean; public searchHighlighted: '' | 'highlighted' | 'sub' | 'hidden'; - public search: string; - public searchChildren: string; + public search: string[]; + public searchChildren: string[]; constructor(data: T, parent: TreeListItem | null, lvl: number) { this.data = data; @@ -23,11 +23,19 @@ export class TreeListItem { this.expandable = false; this.selected = false; this.highlighted = false; - this.search = ''; - this.searchChildren = ''; + this.search = []; + this.searchChildren = []; this.searchHighlighted = ''; } + public getPath(): TreeListItem[] { + if (this.parent) { + return [...this.parent.getPath(), this]; + } else { + return [this]; + } + } + public setChildren(children: TreeListItem[]) { this.children.length = 0; for (const child of children) { @@ -74,12 +82,15 @@ export class TreeListItem { } } - public setSearchRules(f: (item: T) => string) { + public setSearchRules(f: (item: T) => string[]) { this.searchHighlighted = ''; this.search = f(this.data); - this.searchChildren = this.search + '|'; + this.searchChildren = this.search.map((s) => s + '|'); for (const child of this.children) { - this.searchChildren = this.searchChildren + child.searchChildren + '|'; + for (let i = 0; i < this.searchChildren.length; i++) { + this.searchChildren[i] = this.searchChildren[i] + child.searchChildren[i] + '|'; + + } } } } @@ -159,20 +170,20 @@ export class TreeListData { this._items = list.filter((i) => !i.hidden); } - public setSearchRules(f: (item: T) => string) { + 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): void { + public searchItems(text: string, ruleIndex: number): void { if (text) { for (const item of this.list) { - if (item.search.includes(text)) { + if (item.search[ruleIndex].includes(text)) { item.searchHighlighted = 'highlighted'; } else { - if (item.searchChildren.includes(text)) { + if (item.searchChildren[ruleIndex].includes(text)) { item.searchHighlighted = 'sub'; item.collapsed = false; } else { @@ -187,7 +198,11 @@ export class TreeListData { } } - public static fromObject(object: any, field: string): TreeListData { + public static fromObject( + root: any, + field: string, + f?: (item: TreeListItem) => TreeListItem + ): TreeListData { const list: TreeListItem[] = []; const getItem = ( data: any, @@ -196,19 +211,22 @@ export class TreeListData { lvl: number ): TreeListItem[] => { const children: TreeListItem[] = [] - const array: any[] = data[key]; - if (Array.isArray(array)) { - for (const f of array) { - const i = new TreeListItem(f, parent, lvl); - list.push(i); - children.push(i); - const c = getItem(f, field, i, lvl + 1); - i.setChildren(c); + 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(object, field, null, 0) + getItem(root, field, null, 0) return new TreeListData(list); } } @@ -219,6 +237,7 @@ export class TreeListView { private _selectedFields: TreeListItem[] = []; private _selectedCount = 0; + private _selectedAllCount = 0; private _selectedLimit = 0; private _views: TreeListView[]; private _search: string; @@ -232,6 +251,10 @@ export class TreeListView { return this._selectedCount; } + public get selectedAllCount(): number { + return this._selectedAllCount; + } + public get items(): TreeListItem[] { return this._data.items; } @@ -296,28 +319,7 @@ export class TreeListView { return this._data.find(f); } - public filterOne(text: string): TreeListItem | null { - for (const index of this._indexes) { - const item = this._data.list[index]; - if (item.search.includes(text)) { - return item; - } - } - return null; - } - - public filter(text: string): TreeListItem[] { - const result: TreeListItem[] = [] - for (const index of this._indexes) { - const item = this._data.list[index]; - if (item.search.includes(text)) { - result.push(item); - } - } - return result; - } - - public setSearchRules(f: (item: T) => string) { + public setSearchRules(f: (item: T) => string[]) { this._data.setSearchRules(f); } @@ -326,25 +328,19 @@ export class TreeListView { for (const index of this._indexes) { const item = this._data.list[index]; if (item) { - this._search = this._search + item.search + '|'; + this._search = this._search + item.search[0] + '|'; } } } - public searchItems(text: string): void { - this._data.searchItems(text); + public searchItems(text: string, ruleIndex: number): void { + this._data.searchItems(text, ruleIndex); } public searchView(text: string): void { this._searchHighlighted = false; - if (text) { - for (const index of this._indexes) { - const item = this._data.list[index]; - if (item.search.includes(text)) { - this._searchHighlighted = true; - return; - } - } + if (text && this._search.includes(text)) { + this._searchHighlighted = true; } } @@ -354,6 +350,7 @@ export class TreeListView { 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) { @@ -364,6 +361,10 @@ export class TreeListView { } } + public getSelected(): TreeListItem[] { + return this._data.list.filter((item) => item.selected); + } + public createView(f: (item: TreeListItem) => boolean): TreeListView { const indexes: number[] = []; diff --git a/frontend/src/app/services/branding.service.ts b/frontend/src/app/services/branding.service.ts index 6583835b06..2e61d1cfcb 100644 --- a/frontend/src/app/services/branding.service.ts +++ b/frontend/src/app/services/branding.service.ts @@ -105,10 +105,10 @@ export class BrandingService { } if (companyLogo) { companyLogo.style.display = 'none'; - } - if (brandingData.companyLogoUrl) { - companyLogo.style.display = 'block'; - companyLogo.src = brandingData.companyLogoUrl; + if (brandingData.companyLogoUrl) { + companyLogo.style.display = 'block'; + companyLogo.src = brandingData.companyLogoUrl; + } } if (this.brandingData?.companyLogoUrl) { favicon[0].href = this.brandingData.companyLogoUrl; diff --git a/frontend/src/app/themes/guardian/checkbox.scss b/frontend/src/app/themes/guardian/checkbox.scss index 1b62185f38..e9fa1525fc 100644 --- a/frontend/src/app/themes/guardian/checkbox.scss +++ b/frontend/src/app/themes/guardian/checkbox.scss @@ -18,6 +18,10 @@ } } + .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; @@ -35,4 +39,52 @@ } } } +} + +.guardian-radio-button { + .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; + } + } + } + } } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/index.scss b/frontend/src/app/themes/guardian/index.scss index 9aa6e9e268..c0f52fa8ba 100644 --- a/frontend/src/app/themes/guardian/index.scss +++ b/frontend/src/app/themes/guardian/index.scss @@ -12,4 +12,5 @@ @import 'tabs.scss'; @import 'checkbox.scss'; @import 'collapse.scss'; + @import 'separator.scss'; } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/separator.scss b/frontend/src/app/themes/guardian/separator.scss new file mode 100644 index 0000000000..652eb30789 --- /dev/null +++ b/frontend/src/app/themes/guardian/separator.scss @@ -0,0 +1,67 @@ +.guardian-stepper { + width: 100%; + height: 100%; + display: flex; + user-select: none; + + &.horizontal-stepper { + flex-direction: row; + + .guardian-step-separator { + width: 106px; + position: relative; + + &::before { + content: ""; + display: block; + position: absolute; + border-top: 2px solid #C4D0E1; + left: 32px; + right: 32px; + top: calc(50% - 1px); + height: 3px; + } + } + } + + .guardian-step { + position: relative; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + cursor: pointer; + height: 40px; + + .guardian-step-icon { + width: 24px; + height: 24px; + background: #AAB7C4; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + } + + .guardian-step-name { + height: 24px; + padding: 2px 8px; + font-family: Inter; + font-size: 14px; + font-weight: 500; + text-align: left; + color: #AAB7C4; + } + + + &[action="true"] { + .guardian-step-icon { + background: var(--guardian-primary-color, #4169E2); + } + + .guardian-step-name { + color: var(--guardian-primary-color, #4169E2); + } + } + } +} \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/center.svg b/frontend/src/assets/images/icons/16/center.svg new file mode 100644 index 0000000000..b67b8fda66 --- /dev/null +++ b/frontend/src/assets/images/icons/16/center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/16/minus.svg b/frontend/src/assets/images/icons/16/minus.svg new file mode 100644 index 0000000000..71e1359f20 --- /dev/null +++ b/frontend/src/assets/images/icons/16/minus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/images/icons/16/right-arrow.svg b/frontend/src/assets/images/icons/16/right-arrow.svg new file mode 100644 index 0000000000..2ccc226cde --- /dev/null +++ b/frontend/src/assets/images/icons/16/right-arrow.svg @@ -0,0 +1,4 @@ + + + From 7d332520916224cb7ba97e5700e2e32f9d185a9d Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 16 Sep 2024 18:45:05 +0400 Subject: [PATCH 14/48] update Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 96 +++++++++- api-gateway/src/helpers/guardians.ts | 25 +++ .../validation/schemas/statistics.dto.ts | 87 +++++++++ common/src/entity/policy-statistic.ts | 26 ++- ...cy-statistics-configuration.component.html | 14 +- ...cy-statistics-configuration.component.scss | 10 +- ...licy-statistics-configuration.component.ts | 147 ++++++++++----- .../schema-node.ts | 168 +++++++++++++----- .../tree-graph/tree-graph.component.html | 3 +- .../tree-graph/tree-graph.component.scss | 62 ++++--- .../tree-graph/tree-graph.component.ts | 110 ++++-------- .../policy-statistics/tree-graph/tree-line.ts | 1 - .../policy-statistics/tree-graph/tree-list.ts | 13 +- .../tree-graph/tree-source.ts | 41 +++++ .../app/services/policy-statistics.service.ts | 12 +- .../src/app/themes/guardian/separator.scss | 13 +- .../src/api/statistics.service.ts | 82 +++++++++ .../src/type/messages/message-api.type.ts | 2 + 18 files changed, 694 insertions(+), 218 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/tree-graph/tree-source.ts diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index 7075b0c2c7..ffa21d7fcb 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -1,5 +1,5 @@ import { IAuthUser, PinoLogger } from '@guardian/common'; -import { Body, Controller, Get, HttpCode, HttpException, HttpStatus, Param, Post, Query, Req, Response } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Req, Response } from '@nestjs/common'; import { Permissions } from '@guardian/interfaces'; import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; import { Examples, InternalServerErrorDTO, StatisticsDTO, pageHeader } from '#middlewares'; @@ -195,4 +195,98 @@ export class PolicyStatisticsApi { await InternalException(error, this.logger); } } + + /** + * Update statistic + */ + @Put('/:id') + @Auth(Permissions.STATISTICS_STATISTIC_CREATE) + @ApiOperation({ + summary: 'Updates statistic configuration.', + description: 'Updates statistic configuration for the specified statistic ID.' + ONLY_SR, + }) + @ApiParam({ + name: 'id', + type: 'string', + required: true, + description: 'Statistic Identifier', + example: Examples.DB_ID, + }) + @ApiBody({ + description: 'Object that contains a statistic.', + required: true, + type: StatisticsDTO + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: StatisticsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async updateStatistic( + @AuthUser() user: IAuthUser, + @Param('id') id: string, + @Body() item: StatisticsDTO + ): Promise { + try { + if (!id) { + throw new HttpException('Invalid statistic id', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getStatisticById(id, owner); + if (!oldItem) { + throw new HttpException('Statistic not found.', HttpStatus.NOT_FOUND); + } + return await guardians.updateStatistic(id, item, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Delete statistic + */ + @Delete('/:id') + @Auth(Permissions.STATISTICS_STATISTIC_CREATE) + @ApiOperation({ + summary: 'Deletes the statistic.', + description: 'Deletes the statistic with the provided statistic ID.' + ONLY_SR, + }) + @ApiParam({ + name: 'id', + type: 'string', + required: true, + description: 'Statistic Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async deleteStatistic( + @AuthUser() user: IAuthUser, + @Param('id') id: string + ): Promise { + try { + if (!id) { + throw new HttpException('Invalid statistic id', HttpStatus.UNPROCESSABLE_ENTITY) + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.deleteStatistic(id, 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 b035f50852..87f10dc12d 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -2879,4 +2879,29 @@ export class Guardians extends NatsService { public async getStatisticRelationships(id: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.GET_STATISTIC_RELATIONSHIPS, { id, owner }); } + + /** + * Update statistic + * @param id + * @param statistic + * @param owner + * @returns theme + */ + public async updateStatistic( + id: string, + statistic: any, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.UPDATE_STATISTIC, { id, statistic, owner }); + } + + /** + * Delete statistic + * @param id + * @param owner + * @returns Operation Success + */ + public async deleteStatistic(id: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.DELETE_STATISTIC, { id, owner }); + } } diff --git a/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts b/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts index b2e988206a..90379bb777 100644 --- a/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/statistics.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Examples } from '../examples.js'; +import { IsObject, IsOptional, IsString } from 'class-validator'; export class StatisticsDTO { @ApiProperty({ @@ -14,6 +15,8 @@ export class StatisticsDTO { required: false, example: Examples.UUID }) + @IsOptional() + @IsString() uuid?: string; @ApiProperty({ @@ -21,6 +24,7 @@ export class StatisticsDTO { required: true, example: 'Tool name' }) + @IsString() name: string; @ApiProperty({ @@ -28,5 +32,88 @@ export class StatisticsDTO { 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.ACCOUNT_ID + }) + @IsOptional() + @IsString() + topicId?: 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() + instanceTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Draft' + }) + @IsOptional() + @IsString() + status?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: '' + }) + @IsOptional() + @IsString() + method?: string; + + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + config?: any; } \ No newline at end of file diff --git a/common/src/entity/policy-statistic.ts b/common/src/entity/policy-statistic.ts index bb2782f2a7..0775b7c211 100644 --- a/common/src/entity/policy-statistic.ts +++ b/common/src/entity/policy-statistic.ts @@ -8,25 +8,25 @@ import { GenerateUUIDv4 } from '@guardian/interfaces'; @Entity() export class PolicyStatistic extends BaseEntity { /** - * Tag id + * ID */ @Property({ nullable: true }) uuid?: string; /** - * Tag label + * Label */ @Property({ nullable: true }) name?: string; /** - * Tag description + * Description */ @Property({ nullable: true }) description?: string; /** - * Tag owner + * Owner */ @Property({ nullable: true, @@ -35,13 +35,13 @@ export class PolicyStatistic extends BaseEntity { owner?: string; /** - * Tool creator + * Creator */ @Property({ nullable: true }) creator?: string; /** - * Target ID + * Status */ @Property({ nullable: true }) status?: 'Draft' | 'Published'; @@ -80,7 +80,19 @@ export class PolicyStatistic extends BaseEntity { instanceTopicId?: string; /** - * Set policy defaults + * Config + */ + @Property({ nullable: true, type: 'unknown' }) + config?: any; + + /** + * Method + */ + @Property({ nullable: true }) + method?: string; + + /** + * Set defaults */ @BeforeCreate() setDefaults() { diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 2308a77a43..2aea4fc902 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -130,7 +130,7 @@
-
+
-
+
@@ -395,16 +395,16 @@ {{variable.id}}
- {{variable.namePath}} + {{variable.schemaPath}}
- {{variable.propertyName}} + {{variable.fieldPropertyName}}
- {{variable.description}} + {{variable.fieldDescription}}
- {{variable.type}} + {{variable.fieldType}}
@@ -496,6 +496,6 @@
- +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index ef782bbd2f..02f6c337bb 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -320,6 +320,8 @@ -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; @@ -386,6 +388,8 @@ bottom: 0; left: 0; right: 0; + transform: translateZ(0); + transform-origin: 0% 0%; .field-item { width: 100%; @@ -643,7 +647,7 @@ .tree-node { background: #ffffff; - border: 1px solid #E1E7EF; + border: 1px solid #bac0ce; box-shadow: 0px 4px 4px 0px #00000014; border-radius: 6px; width: 150px; @@ -694,7 +698,7 @@ background: #F9FAFC; font-family: Inter; font-size: 12px; - font-weight: 500; + font-weight: 600; line-height: 14px; text-align: left; color: #23252E; @@ -717,6 +721,7 @@ padding: 8px 8px 8px 16px; overflow: hidden; text-overflow: ellipsis; + font-weight: 400; } .node-not-fields { @@ -725,6 +730,7 @@ padding: 8px 8px 8px 16px; overflow: hidden; text-overflow: ellipsis; + font-weight: 400; } } } diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index 508d90a17b..85965807f9 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -10,6 +10,7 @@ import { TreeListItem } from '../tree-graph/tree-list'; import { SchemaData, SchemaFormulas, SchemaNode, SchemaVariables } from './schema-node'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SchemaService } from 'src/app/services/schema.service'; +import { TreeSource } from '../tree-graph/tree-source'; @Component({ selector: 'app-policy-statistics-configuration', @@ -32,8 +33,8 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private subscription = new Subscription(); private tree: TreeGraphComponent; private nodes: SchemaNode[]; + private source: TreeSource; - public roots: SchemaNode[]; public selectedNode: SchemaNode | null = null; public rootNode: SchemaNode | null = null; @@ -45,8 +46,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; @ViewChild('treeTabs', { static: false }) treeTabs: ElementRef; - private _timeout1: any; - private _timeout2: any; + private _selectTimeout1: any; + private _selectTimeout2: any; + private _selectTimeout3: any; public formulas: SchemaFormulas = new SchemaFormulas(); public variables: SchemaVariables = new SchemaVariables(); @@ -90,6 +92,10 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } } + public get roots(): SchemaNode[] { + return this.source?.roots; + } + constructor( private profileService: ProfileService, private schemaService: SchemaService, @@ -143,8 +149,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { ]).subscribe(([item, relationships, properties]) => { this.item = item; if (relationships) { - this.prepareData(item, relationships, properties); + this.updateTree(relationships, properties); } + this.updateForm(this.item); setTimeout(() => { this.loading = false; }, 1000); @@ -153,16 +160,12 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { }); } - private prepareData( - item: any, - relationships: any, - properties: any[] - ) { + private updateTree(relationships: any, properties: any[]) { this.policy = relationships.policy || {}; this.schemas = relationships.schemas || []; this.nodes = []; this.properties = new Map(); - if(properties) { + if (properties) { for (const property of properties) { this.properties.set(property.title, property.value); } @@ -182,16 +185,38 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } } + this.source = new TreeSource(this.nodes); if (this.tree) { - this.tree.setData(this.nodes); + this.tree.setData(this.source); } + } + private updateForm(item: any) { this.overviewForm.setValue({ name: item.name, description: item.description, policy: this.policy?.name, method: item.description, }); + + const config = item.config || {}; + this.variables.fromData(config.variables); + this.formulas.fromData(config.formulas); + + const map1 = this.variables.getMap(); + for (const root of this.source.roots) { + const map2 = map1.get(root.data.iri); + if (map2) { + const rootView = root.fields; + const data = rootView.data; + for (const field of data.list) { + const path = field.path.map((e) => e.data.name).join('.'); + field.selected = map2.has(path); + } + rootView.updateHidden(); + rootView.updateSelected(); + } + } } public onBack() { @@ -201,34 +226,34 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public initTree($event: TreeGraphComponent) { this.tree = $event; if (this.nodes) { - this.tree.setData(this.nodes); + this.tree.setData(this.source); } } public createNodes($event: any) { - const roots = $event.roots as SchemaNode[]; - const nodes = $event.nodes as SchemaNode[]; - this.roots = roots; - for (const node of nodes) { - node.fields.updateSearch(); - } this.tree.move(18, 46); } public onSelectNode(node: TreeNode | null) { - clearTimeout(this._timeout1); - clearTimeout(this._timeout2); + clearTimeout(this._selectTimeout1); + clearTimeout(this._selectTimeout2); + clearTimeout(this._selectTimeout3); this.nodeLoading = true; this.selectedNode = node as SchemaNode; - this.rootNode = (node?.getRoot() || null) as SchemaNode; + this._selectTimeout1 = setTimeout(() => { + this._updateSelectNode(); + }, 350) + + } + + private _updateSelectNode() { + this.rootNode = (this.selectedNode?.getRoot() || null) as SchemaNode; if (this.rootNode) { - const id = this.selectedNode?.data?.iri; + const schemaId = this.selectedNode?.data?.iri; const rootView = this.rootNode.fields; rootView.collapseAll(true); rootView.highlightAll(false); - const items = rootView.find((item: any) => { - return item.type === id; - }); + const items = rootView.find((item) => item.type === schemaId); for (const item of items) { rootView.collapsePath(item, false); rootView.highlight(item, true); @@ -236,23 +261,29 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { rootView.searchItems(this.searchField, this.schemaFilterType); rootView.updateHidden(); rootView.updateSelected(); - this._timeout1 = setTimeout(() => { - const first = (document as any) - .querySelector('.field-item[highlighted="true"]:not([search-highlighted="hidden"])'); - if (this.fieldTree) { - if (first) { - this.fieldTree.nativeElement.scrollTop = first.offsetTop; - } else { - this.fieldTree.nativeElement.scrollTop = 0; - } - } - this._timeout2 = setTimeout(() => { - this.nodeLoading = false; - }, 200) + this._selectTimeout2 = setTimeout(() => { + this._updateSelectScroll(); }, 200) } } + private _updateSelectScroll() { + const first = (document as any) + .querySelector('.field-item[highlighted="true"]:not([search-highlighted="hidden"])'); + if (this.fieldTree) { + if (first) { + this.fieldTree.nativeElement.scrollTop = first.offsetTop; + } else { + this.fieldTree.nativeElement.scrollTop = 0; + } + } + this._selectTimeout3 = setTimeout(() => { + this.nodeLoading = false; + }, 200) + } + + + public onCollapseField(field: TreeListItem) { if (this.rootNode) { const rootView = this.rootNode.fields; @@ -272,17 +303,16 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } public onSchemaFilter() { - clearTimeout(this._timeout2); + clearTimeout(this._selectTimeout3); this.nodeLoading = true; const value = (this.searchField || '').trim().toLocaleLowerCase(); - if (this.tree) { - const roots = this.tree.getRoots() as SchemaNode[]; + if (this.source) { + const roots = this.source.roots; for (const root of roots) { root.fields.searchItems(value, this.schemaFilterType); } - const nodes = this.tree.getNodes() as SchemaNode[]; - for (const node of nodes) { + for (const node of this.source.nodes) { node.fields.searchView(value); } @@ -291,7 +321,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { rootView.updateHidden(); } } - this._timeout2 = setTimeout(() => { + this._selectTimeout3 = setTimeout(() => { this.nodeLoading = false; }, 200) } @@ -365,10 +395,37 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } private updateVariables() { - this.variables.fromNodes(this.roots); + this.variables.fromNodes(this.source.roots); } public onAddVariable() { this.formulas.add(); } + + public onSave() { + this.loading = true; + const value = this.overviewForm.value; + const config = { + variables: this.variables.getJson(), + formulas: this.formulas.getJson() + }; + const item = { + ...this.item, + name: value.name, + description: value.description, + method: value.method, + config + }; + this.policyStatisticsService + .update(item) + .subscribe((item) => { + this.item = item; + this.updateForm(this.item); + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts index ef45f0c71f..c4d648bb96 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts @@ -41,18 +41,14 @@ export class SchemaNode extends TreeNode { } public override update() { - this.fields = this.getRootFields(); - } - - public getRootFields(): TreeListView { if (this.parent) { - const parentFields = (this.parent as SchemaNode).getRootFields(); - return parentFields.createView((s) => { + const root = this.getRoot() as SchemaNode; + const parentFields = root.fields; + this.fields = parentFields.createView((s) => { return s.parent?.data?.type === this.data.iri; }); - } else { - return this.fields; } + this.fields.updateSearch(); } public static from(schema: Schema, properties: Map): SchemaNode { @@ -85,20 +81,78 @@ export class SchemaNode extends TreeNode { } } -export class SchemaVariable { +interface IVariableData { + id: string; + schemaId: string; + path: string; + schemaName: string; + schemaPath: string; + fieldType: string; + fieldDescription: string; + fieldProperty: string; + fieldPropertyName: string; +} + +export class SchemaVariable implements IVariableData { public id: string; - public path: string; - public namePath: string; public schemaId: string; - public schema: string; - public description: string; - public property: string; - public propertyName: string; - public type: string; + public path: string; + + public schemaName: string; + public schemaPath: string; + public fieldType: string; + public fieldDescription: string; + public fieldProperty: string; + public fieldPropertyName: string; + public index: number; constructor() { } + + public getJson(): IVariableData { + return { + id: this.id, + schemaId: this.schemaId, + path: this.path, + schemaName: this.schemaName, + schemaPath: this.schemaPath, + fieldType: this.fieldType, + fieldDescription: this.fieldDescription, + fieldProperty: this.fieldProperty, + fieldPropertyName: this.fieldPropertyName + } + } + + public static fromData(data: IVariableData): SchemaVariable { + const variable = new SchemaVariable(); + variable.id = data.id; + variable.schemaId = data.schemaId; + variable.path = data.path; + variable.schemaName = data.schemaName; + variable.schemaPath = data.schemaPath; + variable.fieldType = data.fieldType; + variable.fieldDescription = data.fieldDescription; + variable.fieldProperty = data.fieldProperty; + variable.fieldPropertyName = data.fieldPropertyName; + return variable; + } + + public static fromNode(rootNode: SchemaNode, field: TreeListItem): SchemaVariable { + const variable = new SchemaVariable(); + const path = field.path.map((e) => e.data.name).join('.'); + const schemaPath = field.path.map((e) => e.data.description).join(' / '); + variable.id = ''; + variable.schemaId = rootNode.data.iri; + variable.path = path; + variable.schemaName = rootNode.data.name; + variable.schemaPath = schemaPath; + variable.fieldType = field.data.type; + variable.fieldDescription = field.data.description; + variable.fieldProperty = field.data.property; + variable.fieldPropertyName = field.data.propertyName; + return variable; + } } export class SchemaVariables { @@ -125,23 +179,13 @@ export class SchemaVariables { return name; } - public fromData(data: any[]) { + public fromData(data: IVariableData[]) { const map = new Map(); if (data) { for (let index = 0; index < data.length; index++) { const item = data[index]; - const variable = new SchemaVariable(); - variable.id = item.id; - variable.schemaId = item.schemaId; - variable.path = item.path; - variable.namePath = item.namePath; - variable.schema = item.schema; - variable.description = item.description; - variable.type = item.type; - variable.property = item.property; - variable.propertyName = item.propertyName; + const variable = SchemaVariable.fromData(item); variable.index = index; - const fullPath = `${variable.schemaId}.${variable.path}`; map.set(fullPath, variable); } @@ -167,17 +211,7 @@ export class SchemaVariables { const fields = root.fields.getSelected(); for (const field of fields) { index++; - const variable = new SchemaVariable(); - const path = field.getPath(); - variable.id = ''; - variable.schemaId = root.data.iri; - variable.path = path.map((e) => e.data.name).join('.'); - variable.namePath = path.map((e) => e.data.description).join(' / '); - variable.schema = root.data.name; - variable.description = field.data.description; - variable.type = field.data.type; - variable.property = field.data.property; - variable.propertyName = field.data.propertyName; + const variable = SchemaVariable.fromNode(root, field); variable.index = index; const fullPath = `${variable.schemaId}.${variable.path}`; map.set(fullPath, variable); @@ -206,9 +240,33 @@ export class SchemaVariables { this.variables[index].index = index; } } + + public getJson(): any[] { + return this.variables.map((v) => v.getJson()); + } + + public getMap(): Map> { + const map = new Map>(); + for (const variable of this.variables) { + let m = map.get(variable.schemaId); + if (!m) { + m = new Map(); + map.set(variable.schemaId, m); + } + m.set(variable.path, variable); + } + return map; + } +} + +interface IFormulaData { + id: string; + type: string; + description: string; + formula: string; } -export class SchemaFormula { +export class SchemaFormula implements IFormulaData { public id: string; public type: string; public description: string; @@ -217,6 +275,24 @@ export class SchemaFormula { constructor() { } + + public getJson(): IFormulaData { + return { + id: this.id, + type: this.type, + description: this.description, + formula: this.formula + } + } + + public static fromData(data: IFormulaData): SchemaFormula { + const formula = new SchemaFormula(); + formula.id = data.id; + formula.type = data.type; + formula.description = data.description; + formula.formula = data.formula; + return formula; + } } export class SchemaFormulas { @@ -249,16 +325,12 @@ export class SchemaFormulas { this.formulas.push(formula); } - public fromData(data: any[]) { + public fromData(data: IFormulaData[]) { this.formulas = []; if (data) { for (let index = 0; index < data.length; index++) { const item = data[index]; - const formula = new SchemaFormula(); - formula.id = item.id; - formula.type = item.type; - formula.description = item.description; - formula.formula = item.formula; + const formula = SchemaFormula.fromData(item); formula.index = index; this.formulas.push(formula); } @@ -268,4 +340,8 @@ export class SchemaFormulas { } this.startIndex = this.formulas.length + 1; } + + public getJson(): any[] { + return this.formulas.map((f) => f.getJson()); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html index 8887f26ea3..6b0f2f4f0f 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-graph.component.html @@ -1,10 +1,11 @@
; + @ViewChild('gridEl', { static: false }) gridEl: ElementRef; @ContentChild('nodeTemplate') nodeTemplate: TemplateRef; @Output('init') initEvent = new EventEmitter(); @Output('select') selectEvent = new EventEmitter | null>(); @Output('render') renderEvent = new EventEmitter(); - public roots: TreeNode[]; - public nodes: TreeNode[]; public grid: Grid; public width: number = 200; public zoom = 1; public toolbar = true; + public source: TreeSource; - constructor() { + private _unListen: Function; + + constructor( + private ngZone: NgZone, + private renderer: Renderer2 + ) { } ngOnInit() { + this.ngZone.runOutsideAngular(() => { + this._unListen = this.renderer.listen( + this.movedEl.nativeElement, + 'mousemove', + this.onMouseMove.bind(this) + ); + }); this.initEvent.emit(this); } ngOnDestroy(): void { - + this._unListen(); } public get moving(): boolean { @@ -43,27 +56,18 @@ export class TreeGraphComponent implements OnInit { } } - public setData(nodes: TreeNode[]) { - const { roots, allNodes } = this.nonUniqueNodes(nodes); - this.roots = roots; - this.nodes = allNodes; - this.grid = Grid.createLayout(this.width, this.roots); + public setData(source: TreeSource) { + this.source = source; + this.grid = Grid.createLayout(this.width, this.source.roots); this.grid.render(); this.renderEvent.emit({ grid: this.grid, - roots: this.roots, - nodes: this.nodes + roots: this.source.roots, + nodes: this.source.nodes, + source: this.source }) } - public getNodes(): TreeNode[] { - return this.nodes; - } - - public getRoots(): TreeNode[] { - return this.roots; - } - public select(node: TreeNode | null) { const selected = node && node.selected !== SelectType.SELECTED; if (node && selected) { @@ -83,63 +87,15 @@ export class TreeGraphComponent implements OnInit { } } - private uniqueNodes(nodes: TreeNode[]): TreeNode[] { - const nodeMap = new Map>(); - for (const node of nodes) { - nodeMap.set(node.id, node); - } - - const roots = nodes.filter((n) => n.type === 'root'); - const subs = nodes.filter((n) => n.type !== 'root'); - - return roots; - } - - private nonUniqueNodes(nodes: TreeNode[]) { - const roots = nodes.filter((n) => n.type === 'root'); - const subs = nodes.filter((n) => n.type !== 'root'); - - const nodeMap = new Map>(); - for (const node of subs) { - nodeMap.set(node.id, node); - } - - const allNodes: TreeNode[] = []; - const getSubNode = (node: TreeNode): TreeNode => { - allNodes.push(node); - for (const id of node.childIds) { - const child = nodeMap.get(id); - if (child) { - node.addNode(getSubNode(child.clone())); - } else { - console.log('', id) - } - } - return node; - } - for (const root of roots) { - getSubNode(root); - } - for (const root of roots) { - root.resize(); - } - for (const node of allNodes) { - node.update(); - } - return { roots, allNodes }; - } - public setZoom(zoom: number, el: any) { - let transformOrigin = [0, 0]; - var p = ["webkit", "moz", "ms", "o"], - s = "scale(" + zoom + ")", - oString = (transformOrigin[0] * 100) + "% " + (transformOrigin[1] * 100) + "%"; - - for (var i = 0; i < p.length; i++) { + const transformOrigin = [0, 0]; + const p = ["webkit", "moz", "ms", "o"]; + const s = "translateZ(0) scale(" + zoom + ")"; + const oString = (transformOrigin[0] * 100) + "% " + (transformOrigin[1] * 100) + "%"; + for (let i = 0; i < p.length; i++) { el.style[p[i] + "Transform"] = s; el.style[p[i] + "TransformOrigin"] = oString; } - el.style["transform"] = s; el.style["transformOrigin"] = oString; } @@ -168,7 +124,7 @@ export class TreeGraphComponent implements OnInit { public onMouseDown($event: any) { if ($event.stopPropagation) { - $event.stopPropagation() + $event.stopPropagation(); } this.grid.onMove(true, $event); this.gridEl.nativeElement.style.left = `${this.grid.x}px`; @@ -177,7 +133,7 @@ export class TreeGraphComponent implements OnInit { public onMouseUp($event: any) { if ($event.stopPropagation) { - $event.stopPropagation() + $event.stopPropagation(); } this.grid.onMove(false, $event); this.gridEl.nativeElement.style.left = `${this.grid.x}px`; @@ -186,7 +142,7 @@ export class TreeGraphComponent implements OnInit { public onMouseMove($event: any) { if ($event.stopPropagation) { - $event.stopPropagation() + $event.stopPropagation(); } if (this.grid.onMoving($event)) { this.gridEl.nativeElement.style.left = `${this.grid.x}px`; diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts index ca8eea1605..79c6061a57 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-line.ts @@ -1,7 +1,6 @@ import { SelectType } from "./tree-types"; import { TreeNode } from "./tree-node"; - export class Line { public width: number; public height: number; diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts index 652321f2df..73551ec5d4 100644 --- a/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-list.ts @@ -3,6 +3,7 @@ export class TreeListItem { public readonly parent: TreeListItem | null; public readonly lvl: number; public readonly children: TreeListItem[]; + public readonly path: TreeListItem[]; public collapsed: boolean; public expandable: boolean; @@ -26,14 +27,13 @@ export class TreeListItem { this.search = []; this.searchChildren = []; this.searchHighlighted = ''; - } - public getPath(): TreeListItem[] { if (this.parent) { - return [...this.parent.getPath(), this]; + this.path = [...this.parent.path, this]; } else { - return [this]; + this.path = [this]; } + } public setChildren(children: TreeListItem[]) { @@ -89,7 +89,6 @@ export class TreeListItem { for (const child of this.children) { for (let i = 0; i < this.searchChildren.length; i++) { this.searchChildren[i] = this.searchChildren[i] + child.searchChildren[i] + '|'; - } } } @@ -243,6 +242,10 @@ export class TreeListView { private _search: string; private _searchHighlighted: boolean; + public get data(): TreeListData { + return this._data; + } + public get selectedFields(): TreeListItem[] { return this._selectedFields; } diff --git a/frontend/src/app/modules/policy-statistics/tree-graph/tree-source.ts b/frontend/src/app/modules/policy-statistics/tree-graph/tree-source.ts new file mode 100644 index 0000000000..62df7191a8 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/tree-graph/tree-source.ts @@ -0,0 +1,41 @@ +import { TreeNode } from "./tree-node"; + +export class TreeSource> { + public readonly roots: T[]; + public readonly nodes: T[]; + + constructor(nodes: T[]) { + const roots = nodes.filter((n) => n.type === 'root'); + const subs = nodes.filter((n) => n.type !== 'root'); + + const nodeMap = new Map(); + for (const node of subs) { + nodeMap.set(node.id, node); + } + + const allNodes: T[] = []; + const getSubNode = (node: T): T => { + allNodes.push(node); + for (const id of node.childIds) { + const child = nodeMap.get(id); + if (child) { + node.addNode(getSubNode(child.clone() as T)); + } else { + console.log('', id) + } + } + return node; + } + for (const root of roots) { + getSubNode(root); + } + for (const root of roots) { + root.resize(); + } + for (const node of allNodes) { + node.update(); + } + this.roots = roots; + this.nodes = allNodes; + } +} \ No newline at end of file diff --git a/frontend/src/app/services/policy-statistics.service.ts b/frontend/src/app/services/policy-statistics.service.ts index 3c95eb6887..aa10213740 100644 --- a/frontend/src/app/services/policy-statistics.service.ts +++ b/frontend/src/app/services/policy-statistics.service.ts @@ -48,8 +48,8 @@ export class PolicyStatisticsService { return { page, count }; } - public create(policy: any): Observable { - return this.http.post(`${this.url}/`, policy); + public create(item: any): Observable { + return this.http.post(`${this.url}/`, item); } public getItem(id: string): Observable { @@ -59,4 +59,12 @@ export class PolicyStatisticsService { public getRelationships(id: string): Observable { return this.http.get(`${this.url}/${id}/relationships`); } + + public delete(item: any): Observable { + return this.http.delete(`${this.url}/${item.id}`); + } + + public update(item: any): Observable { + return this.http.put(`${this.url}/${item.id}`, item); + } } diff --git a/frontend/src/app/themes/guardian/separator.scss b/frontend/src/app/themes/guardian/separator.scss index 652eb30789..3e2de6f2a1 100644 --- a/frontend/src/app/themes/guardian/separator.scss +++ b/frontend/src/app/themes/guardian/separator.scss @@ -8,7 +8,7 @@ flex-direction: row; .guardian-step-separator { - width: 106px; + width: 90px; position: relative; &::before { @@ -16,8 +16,8 @@ display: block; position: absolute; border-top: 2px solid #C4D0E1; - left: 32px; - right: 32px; + left: 24px; + right: 24px; top: calc(50% - 1px); height: 3px; } @@ -32,6 +32,10 @@ align-items: center; cursor: pointer; height: 40px; + border-radius: 8px; + padding: 8px; + white-space: nowrap; + cursor: pointer; .guardian-step-icon { width: 24px; @@ -53,6 +57,9 @@ color: #AAB7C4; } + &:hover { + background: #f0f3fc; + } &[action="true"] { .guardian-step-icon { diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index 45272ebf26..c3d97b7682 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -38,6 +38,13 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } }); + /** + * Get statistics + * + * @param {any} msg - filters + * + * @returns {any} - Operation success + */ ApiResponse(MessageAPI.GET_STATISTICS, async (msg: { filters: any, owner: IOwner }) => { try { @@ -82,6 +89,13 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } }); + /** + * Get statistic + * + * @param {any} msg - statistic id + * + * @returns {any} - Operation success + */ ApiResponse(MessageAPI.GET_STATISTIC, async (msg: { id: string, owner: IOwner }) => { try { @@ -93,6 +107,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { if (!item || item.owner !== owner.owner) { return new MessageError('Item does not exist.'); } + console.log(item); return new MessageResponse(item); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); @@ -100,6 +115,13 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } }); + /** + * Get relationships + * + * @param {any} msg - statistic id + * + * @returns {any} - Operation success + */ ApiResponse(MessageAPI.GET_STATISTIC_RELATIONSHIPS, async (msg: { id: string, owner: IOwner }) => { try { @@ -127,4 +149,64 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError(error); } }); + + /** + * Update theme + * + * @param payload - theme + * + * @returns {Theme} theme + */ + ApiResponse(MessageAPI.UPDATE_STATISTIC, + async (msg: { id: string, statistic: any, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid update theme parameters'); + } + const { id, statistic, owner } = msg; + + const item = await DatabaseServer.getStatisticById(id); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + + item.name = statistic.name; + item.description = statistic.description; + item.config = statistic.config; + + const result = await DatabaseServer.updateStatistic(item); + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Delete statistics + * + * @param {any} msg - statistic id + * + * @returns {boolean} - Operation success + */ + ApiResponse(MessageAPI.DELETE_STATISTIC, + async (msg: { id: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid delete theme parameters'); + } + const { id, owner } = msg; + const item = await DatabaseServer.getStatisticById(id); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + await DatabaseServer.removeStatistic(item); + return new MessageResponse(true); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + } diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 1f856f976f..31c3a64a80 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -221,6 +221,8 @@ export enum MessageAPI { GET_STATISTICS = 'GET_STATISTICS', GET_STATISTIC = 'GET_STATISTIC', GET_STATISTIC_RELATIONSHIPS = 'GET_STATISTIC_RELATIONSHIPS', + UPDATE_STATISTIC = 'UPDATE_STATISTIC', + DELETE_STATISTIC = 'DELETE_STATISTIC', } /** From c97eff47da1b300044402ccf61c8fe5c9a8b3390 Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Mon, 16 Sep 2024 14:47:50 +0000 Subject: [PATCH 15/48] [skip ci] Add swagger.yaml --- swagger.yaml | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 177fcb60f1..5251e096bd 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -12099,6 +12099,81 @@ paths: tags: *ref_27 security: - bearer: [] + put: + operationId: PolicyStatisticsApi_updateStatistic + summary: Updates statistic configuration. + description: >- + Updates statistic configuration for the specified statistic ID. Only + users with the Standard Registry role are allowed to make the request. + parameters: + - name: id + required: true + in: path + description: Statistic Identifier + example: '000000000000000000000001' + schema: + type: string + requestBody: + required: true + description: Object that contains a statistic. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticsDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_27 + security: + - bearer: [] + delete: + operationId: PolicyStatisticsApi_deleteStatistic + summary: Deletes the statistic. + description: >- + Deletes the statistic with the provided statistic ID. Only users with + the Standard Registry role are allowed to make the request. + parameters: + - name: id + required: true + in: path + description: Statistic 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_27 + security: + - bearer: [] /policy-statistics/{id}/relationships: get: operationId: PolicyStatisticsApi_getStatisticRelationships @@ -15458,6 +15533,35 @@ components: 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 + topicId: + type: string + example: 0.0.1 + messageId: + type: string + example: '0000000000.000000001' + policyId: + type: string + example: '000000000000000000000001' + instanceTopicId: + type: string + example: 0.0.1 + status: + type: string + example: Draft + method: + type: string + example: '' + config: + type: object + nullable: true required: - name WorkersTasksDTO: From 170e1cdcf6f7e63a3a0c1814c99daf72049447c9 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 17 Sep 2024 16:00:32 +0400 Subject: [PATCH 16/48] update Signed-off-by: Stepan Kiryakov --- ...cy-statistics-configuration.component.html | 111 ++++++++++-------- ...cy-statistics-configuration.component.scss | 105 +++++++++++++---- ...licy-statistics-configuration.component.ts | 34 +++++- .../schema-node.ts | 53 ++++++++- .../src/app/themes/guardian/dropdown.scss | 17 +++ frontend/src/app/themes/guardian/input.scss | 34 ++++++ 6 files changed, 281 insertions(+), 73 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 2aea4fc902..ce20ab8590 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -31,7 +31,23 @@
- +
+
+
+
+
Overview
+
+
+
+
Select Schemas
+
+
+
+
Configure Rules
+
+
+
+
@@ -381,7 +397,7 @@
Input
-
+
ID
SCHEMA
@@ -404,11 +420,14 @@ {{variable.fieldDescription}}
- {{variable.fieldType}} + {{variable.displayType}}
+
+ There were no fields selected yet +
@@ -425,21 +444,54 @@
- + +
+
+
+ + +
-
{{formula.type}}
- + +
- + +
-
- - +
+
@@ -463,39 +515,6 @@
-
-
-
-
- - -
-
Overview
-
-
-
-
- - -
-
Select Schemas
-
-
-
-
- - -
-
Configure Rules
-
-
-
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss index 02f6c337bb..0710f2f109 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.scss @@ -60,6 +60,18 @@ } } + .step-container { + min-height: 40px; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + padding: 0px; + background: #fff; + border-bottom: 1px solid #E1E7EF; + position: relative; + } + .body-container { width: 100%; height: 100%; @@ -92,16 +104,17 @@ .tree-container { position: absolute; left: 0px; - top: 39px; - bottom: 0px; + top: 0px; + bottom: 40px; right: 0px; z-index: 1; + user-select: none; } .schema-search { position: absolute; left: 0px; - top: 40px; + top: 0px; right: 0px; height: 56px; z-index: 2; @@ -121,16 +134,17 @@ .schema-toolbar { position: absolute; left: 0px; - top: 0px; + bottom: 0px; right: 0px; - height: 39px; + height: 40px; z-index: 2; background: #F9FAFC; padding: 8px 16px 0px 16px; display: flex; flex-direction: row; - border-bottom: 1px solid #C4D0E1; + border-top: 1px solid #E1E7EF; user-select: none; + box-shadow: 0px 0px 4px 0px #00000014; .tree-tabs { display: flex; @@ -196,7 +210,7 @@ } .tree-tab { - padding: 8px 8px 8px 16px; + padding: 8px 16px 8px 16px; background: #FFFFFF; font-family: Inter; font-size: 12px; @@ -211,10 +225,10 @@ border-bottom-right-radius: 0; height: 30px; cursor: pointer; - margin-right: 4px; + margin-right: 8px; display: flex; - min-width: 110px; - max-width: 110px; + min-width: 140px; + max-width: 140px; &.selected-type-selected, &.selected-type-sub { @@ -249,8 +263,8 @@ } .tree-tab-last { - max-width: 300px; - min-width: 300px; + max-width: 600px; + min-width: 600px; height: 30px; } } @@ -260,7 +274,7 @@ height: 160px; position: absolute; right: 400px; - top: 39px; + top: 0px; z-index: 3; padding: 16px 16px 4px 4px; overflow: hidden; @@ -307,7 +321,7 @@ .schema-fields { position: absolute; - top: 39px; + top: 0px; bottom: 0px; right: 0px; width: 400px; @@ -536,6 +550,17 @@ padding-bottom: 16px; } + .variables-empty-grid { + font-family: Inter; + font-size: 14px; + font-weight: 400; + color: #848FA9; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + } + .variables-grid { .cell-48 { min-width: 48px; @@ -567,19 +592,59 @@ max-width: 300px; } + .cell-btn { + min-width: 48px !important; + width: 48px !important; + max-width: 48px !important; + padding: 6px 10px !important; + + button { + min-width: 36px; + min-height: 36px; + } + } + .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; - input { - border: none; - outline: none; - box-shadow: none; - width: 100%; - padding: 8px 0px; + .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-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; } } diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts index 85965807f9..e5a644cb56 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.ts @@ -28,7 +28,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public id: string; public item: any; public policy: any; - public schemas: any[]; + public schemas: Schema[]; private subscription = new Subscription(); private tree: TreeGraphComponent; @@ -82,6 +82,20 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { value: 'everyYear' }]; + public formulaTypes: any[] = [{ + label: 'String', + value: 'string' + }, { + label: 'Number', + value: 'number' + }, { + label: 'Array(String)', + value: 'array(string)' + }, { + label: 'Array(Number)', + value: 'array(number)' + }]; + public properties: Map; public get zoom(): number { @@ -162,7 +176,6 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private updateTree(relationships: any, properties: any[]) { this.policy = relationships.policy || {}; - this.schemas = relationships.schemas || []; this.nodes = []; this.properties = new Map(); if (properties) { @@ -170,7 +183,9 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { this.properties.set(property.title, property.value); } } - for (const schema of this.schemas) { + const schemas = relationships.schemas || []; + this.schemas = []; + for (const schema of schemas) { try { const item = new Schema(schema); const node = SchemaNode.from(item, this.properties); @@ -180,6 +195,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { } } this.nodes.push(node); + this.schemas.push(item) } catch (error) { console.log(error); } @@ -202,6 +218,7 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { const config = item.config || {}; this.variables.fromData(config.variables); this.formulas.fromData(config.formulas); + this.variables.updateType(this.schemas); const map1 = this.variables.getMap(); for (const root of this.source.roots) { @@ -340,15 +357,15 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { public onNavNext(dir: number) { const el = this.treeTabs.nativeElement; - const max = Math.floor((el.scrollWidth - el.offsetWidth) / 114); - let current = Math.floor(this.treeTabs.nativeElement.scrollLeft / 114); + const max = Math.floor((el.scrollWidth - el.offsetWidth) / 148); + let current = Math.floor(this.treeTabs.nativeElement.scrollLeft / 148); if (dir < 0) { current--; } else { current++; } current = Math.min(Math.max(current, 0), max); - this.treeTabs.nativeElement.scrollLeft = current * 114; + this.treeTabs.nativeElement.scrollLeft = current * 148; } public onClearNode() { @@ -396,12 +413,17 @@ export class PolicyStatisticsConfigurationComponent implements OnInit { private updateVariables() { this.variables.fromNodes(this.source.roots); + this.variables.updateType(this.schemas); } public onAddVariable() { this.formulas.add(); } + public onDeleteVariable(formula:any) { + this.formulas.delete(formula); + } + public onSave() { this.loading = true; const value = this.overviewForm.value; diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts index c4d648bb96..e81ee00ee6 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts @@ -14,6 +14,8 @@ export interface FieldData { description: string; property: string; propertyName: string; + isArray: boolean; + isRef: boolean; } export class SchemaRules { @@ -88,6 +90,8 @@ interface IVariableData { schemaName: string; schemaPath: string; fieldType: string; + fieldRef: boolean; + fieldArray: boolean; fieldDescription: string; fieldProperty: string; fieldPropertyName: string; @@ -101,15 +105,30 @@ export class SchemaVariable implements IVariableData { public schemaName: string; public schemaPath: string; public fieldType: string; + public fieldRef: boolean; + public fieldArray: boolean; public fieldDescription: string; public fieldProperty: string; public fieldPropertyName: string; + public displayType: string; public index: number; constructor() { } + public updateType(schemas: Map) { + const schema = schemas.get(this.fieldType); + if (schema) { + this.displayType = schema.name || this.fieldType; + } else { + this.displayType = this.fieldType; + } + if (this.fieldArray) { + this.displayType = `Array(${this.displayType})`; + } + } + public getJson(): IVariableData { return { id: this.id, @@ -118,6 +137,8 @@ export class SchemaVariable implements IVariableData { schemaName: this.schemaName, schemaPath: this.schemaPath, fieldType: this.fieldType, + fieldArray: this.fieldArray, + fieldRef: this.fieldRef, fieldDescription: this.fieldDescription, fieldProperty: this.fieldProperty, fieldPropertyName: this.fieldPropertyName @@ -131,6 +152,8 @@ export class SchemaVariable implements IVariableData { variable.path = data.path; variable.schemaName = data.schemaName; variable.schemaPath = data.schemaPath; + variable.fieldRef = data.fieldRef; + variable.fieldArray = data.fieldArray; variable.fieldType = data.fieldType; variable.fieldDescription = data.fieldDescription; variable.fieldProperty = data.fieldProperty; @@ -147,6 +170,8 @@ export class SchemaVariable implements IVariableData { variable.path = path; variable.schemaName = rootNode.data.name; variable.schemaPath = schemaPath; + variable.fieldRef = field.data.isRef; + variable.fieldArray = field.data.isArray; variable.fieldType = field.data.type; variable.fieldDescription = field.data.description; variable.fieldProperty = field.data.property; @@ -257,6 +282,16 @@ export class SchemaVariables { } return map; } + + public updateType(schemas: Schema[]) { + const map = new Map(); + for (const schema of schemas) { + map.set(schema.iri, schema) + } + for (const variable of this.variables) { + variable.updateType(map); + } + } } interface IFormulaData { @@ -274,6 +309,7 @@ export class SchemaFormula implements IFormulaData { public index: number; constructor() { + this.type = 'string'; } public getJson(): IFormulaData { @@ -288,7 +324,7 @@ export class SchemaFormula implements IFormulaData { public static fromData(data: IFormulaData): SchemaFormula { const formula = new SchemaFormula(); formula.id = data.id; - formula.type = data.type; + formula.type = data.type || 'string'; formula.description = data.description; formula.formula = data.formula; return formula; @@ -307,6 +343,12 @@ export class SchemaFormulas { this.names = new Set(); } + public setDefault() { + this.names.clear(); + this.startIndex = 1; + this.add(); + } + public getName(): string { let name: string = ''; for (let index = this.startIndex; index < 1000000; index++) { @@ -324,6 +366,12 @@ export class SchemaFormulas { formula.id = this.getName(); this.formulas.push(formula); } + public delete(formula: SchemaFormula) { + this.formulas = this.formulas.filter((f) => f !== formula); + if (this.formulas.length === 0) { + this.setDefault() + } + } public fromData(data: IFormulaData[]) { this.formulas = []; @@ -339,6 +387,9 @@ export class SchemaFormulas { this.names.add(item.id); } this.startIndex = this.formulas.length + 1; + if (this.formulas.length === 0) { + this.setDefault() + } } public getJson(): any[] { diff --git a/frontend/src/app/themes/guardian/dropdown.scss b/frontend/src/app/themes/guardian/dropdown.scss index bd536c7b87..2819ac980c 100644 --- a/frontend/src/app/themes/guardian/dropdown.scss +++ b/frontend/src/app/themes/guardian/dropdown.scss @@ -27,6 +27,16 @@ color: var(--guardian-font-color, #23252E); } } + + .p-inputtext { + font-family: Inter; + font-size: 14px; + font-weight: 400; + padding: 0px 16px; + display: flex; + color: #000; + align-items: center; + } } .guardian-dropdown-selected { @@ -54,6 +64,13 @@ } .guardian-dropdown-item {} + + &.guardian-dropdown-cell { + width: 100%; + .p-dropdown { + border: none !important; + } + } } .guardian-dropdown-panel { diff --git a/frontend/src/app/themes/guardian/input.scss b/frontend/src/app/themes/guardian/input.scss index edf610b711..86d289e36f 100644 --- a/frontend/src/app/themes/guardian/input.scss +++ b/frontend/src/app/themes/guardian/input.scss @@ -18,6 +18,11 @@ 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; &:enabled:hover { border-color: var(--guardian-primary-color, #4169E2); @@ -34,6 +39,35 @@ } } +.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: 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; + } +} + .guardian-search { display: flex; flex-direction: column; From f5727d06ad50899debf90fdb7f5cde92400e5b3a Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 17 Sep 2024 19:28:19 +0400 Subject: [PATCH 17/48] update Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 2 - frontend/src/app/app.component.scss | 9 ++++ frontend/src/app/app.component.ts | 1 + .../lang-modes/autocomplete.ts | 28 +++++++++++ .../lang-modes/formula-lang.mode.ts | 30 ++++++++++++ ...cy-statistics-configuration.component.html | 37 ++++++++++++-- ...cy-statistics-configuration.component.scss | 25 ++++++++++ ...licy-statistics-configuration.component.ts | 49 ++++++++++++++++--- .../schema-node.ts | 8 +++ .../policy-statistics.module.ts | 2 + .../src/app/themes/guardian/separator.scss | 25 +++++++++- .../src/assets/images/icons/16/file-fill.svg | 3 ++ frontend/src/assets/images/icons/16/table.svg | 3 ++ frontend/src/assets/images/icons/16/tree.svg | 3 ++ frontend/src/styles.scss | 1 + 15 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/lang-modes/autocomplete.ts create mode 100644 frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts create mode 100644 frontend/src/assets/images/icons/16/file-fill.svg create mode 100644 frontend/src/assets/images/icons/16/table.svg create mode 100644 frontend/src/assets/images/icons/16/tree.svg diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index ffa21d7fcb..271462115c 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -90,7 +90,6 @@ export class PolicyStatisticsApi { }) @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - @UseCache() async getStatistics( @AuthUser() user: IAuthUser, @Response() res: any, @@ -136,7 +135,6 @@ export class PolicyStatisticsApi { }) @ApiExtraModels(StatisticsDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - @UseCache() async getStatisticById( @AuthUser() user: IAuthUser, @Param('id') id: string diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index 7ca58a24f7..2921c4d90d 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -425,4 +425,13 @@ body { border-bottom-left-radius: 16px; border-bottom-right-radius: 16px; } +} + +::ng-deep body .CodeMirror-hint { + padding: 4px 8px; + min-width: 60px; +} + +::ng-deep body .CodeMirror-hint.CodeMirror-hint-active { + background: #4169E2 !important; } \ No newline at end of file diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index d939eab645..4a37f93b44 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -4,6 +4,7 @@ import { WebSocketService } from './services/web-socket.service'; import { BrandingService } from './services/branding.service'; import './modules/policy-engine/policy-lang-modes/policy-json-lang.mode'; import './modules/policy-engine/policy-lang-modes/policy-yaml-lang.mode'; +import './modules/policy-statistics/lang-modes/formula-lang.mode'; import { globalLoaderActive } from './static/global-loader.function'; import { ActivatedRoute } from '@angular/router'; import { MatIconRegistry } from '@angular/material/icon'; diff --git a/frontend/src/app/modules/policy-statistics/lang-modes/autocomplete.ts b/frontend/src/app/modules/policy-statistics/lang-modes/autocomplete.ts new file mode 100644 index 0000000000..c265ca6c83 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/lang-modes/autocomplete.ts @@ -0,0 +1,28 @@ +import CodeMirror from 'codemirror'; + +export const createAutocomplete = (words: string[]) => { + return (cm: any, option: any) => { + return new Promise(function (accept) { + setTimeout(function () { + const cursor = cm.getCursor(); + const line = cm.getLine(cursor.line); + let start = cursor.ch; + let end = cursor.ch; + while (start && /\w/.test(line.charAt(start - 1))) --start; + while (end < line.length && /\w/.test(line.charAt(end))) ++end; + const word = line.slice(start, end).toLowerCase(); + const r = new RegExp(`^${word}`, 'i'); + const list = words.filter((w) => r.test(w)); + if (list.length) { + return accept({ + list, + from: CodeMirror.Pos(cursor.line, start), + to: CodeMirror.Pos(cursor.line, end) + }) + } else { + return accept(null); + } + }, 100) + }) + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts new file mode 100644 index 0000000000..b09c2a16ef --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts @@ -0,0 +1,30 @@ +import 'codemirror/addon/mode/simple'; +import 'codemirror/addon/mode/overlay'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/css-hint'; +import 'codemirror/mode/meta'; +import CodeMirror, { Mode, StringStream } from 'codemirror'; + +CodeMirror.defineMode('formula-lang', function (config, parserConfig) { + const variables = (config as any).variables as string[]; + const variablesName = variables.map((v) => `(${v})`).join('|'); + const isVariables = new RegExp(variablesName); + const policySyntaxOverlay: Mode = { + token: function (stream: StringStream) { + if (stream.match(isVariables)) { + return 'formula-variable'; + } else if(stream.match(/([a-zA-Z]+)\(/, false)) { + stream.next(); + return 'formula-function'; + } else { + stream.next(); + return null; + } + }, + }; + return CodeMirror.overlayMode( + //text/x-spreadsheet + CodeMirror.getMode(config, parserConfig.backdrop || 'application/ld+json'), + policySyntaxOverlay + ); +}); \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index ce20ab8590..8574619e01 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -35,14 +35,32 @@
+
+ + +
Overview
+
+ + +
Select Schemas
+
+ + +
Configure Rules
@@ -408,7 +426,13 @@
- {{variable.id}} + +
{{variable.schemaPath}} @@ -420,7 +444,9 @@ {{variable.fieldDescription}}
- {{variable.displayType}} + + {{variable.displayType}} +
@@ -474,13 +500,16 @@
- -
+
--> +
+ + + {{row[column.id]}} @@ -137,6 +143,7 @@
+ { + return !(value === EntityStatus.ERROR); + } + }, { + label: 'Published', + value: EntityStatus.PUBLISHED, + description: 'Release version into public domain.', + disable: (value: string): boolean => { + return !(value === EntityStatus.DRAFT); + } + }, { + label: 'Error', + value: EntityStatus.ERROR, + description: '', + disable: true + }] private subscription = new Subscription(); @@ -67,7 +87,7 @@ export class PolicyStatisticsComponent implements OnInit { size: 'auto', tooltip: false }, { - id: 'trigger', + id: 'method', title: 'Trigger', type: 'text', size: 'auto', @@ -220,4 +240,8 @@ export class PolicyStatisticsComponent implements OnInit { public onEdit(item: any) { this.router.navigate(['/policy-statistics', item.id]); } + + public onChangeStatus($event: string): void { + debugger + } } diff --git a/frontend/src/app/themes/guardian/dropdown.scss b/frontend/src/app/themes/guardian/dropdown.scss index 2819ac980c..ea48a8be2e 100644 --- a/frontend/src/app/themes/guardian/dropdown.scss +++ b/frontend/src/app/themes/guardian/dropdown.scss @@ -28,7 +28,7 @@ } } - .p-inputtext { + .p-inputtext { font-family: Inter; font-size: 14px; font-weight: 400; @@ -67,6 +67,7 @@ &.guardian-dropdown-cell { width: 100%; + .p-dropdown { border: none !important; } diff --git a/guardian-service/src/api/statistics.service.ts b/guardian-service/src/api/statistics.service.ts index c3d97b7682..077a9d7625 100644 --- a/guardian-service/src/api/statistics.service.ts +++ b/guardian-service/src/api/statistics.service.ts @@ -1,6 +1,6 @@ import { ApiResponse } from './helpers/api-response.js'; -import { DatabaseServer, ImportExportUtils, MessageError, MessageResponse, PinoLogger, PolicyImportExport } from '@guardian/common'; -import { IOwner, MessageAPI, PolicyType, SchemaStatus } from '@guardian/interfaces'; +import { DatabaseServer, MessageError, MessageResponse, PinoLogger, PolicyImportExport, PolicyStatistic } from '@guardian/common'; +import { EntityStatus, IOwner, MessageAPI, PolicyType, SchemaStatus } from '@guardian/interfaces'; /** * Connect to the message broker methods of working with statistics. @@ -14,22 +14,31 @@ export async function statisticsAPI(logger: PinoLogger): Promise { * @returns {any} new statistic */ ApiResponse(MessageAPI.CREATE_STATISTIC, - async (msg: { statistic: any, owner: IOwner }) => { + async (msg: { statistic: PolicyStatistic, owner: IOwner }) => { try { if (!msg) { - throw new Error('Invalid Params'); + return new MessageError('Invalid parameters'); } const { statistic, owner } = msg; - if (statistic) { - delete statistic._id; - delete statistic.id; - delete statistic.status; - delete statistic.owner; - delete statistic.messageId; + + if (!statistic) { + return new MessageError('Invalid object.'); + } + + const policyId = statistic.policyId; + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); } + + delete statistic._id; + delete statistic.id; + delete statistic.status; + delete statistic.owner; + delete statistic.messageId; statistic.creator = owner.creator; statistic.owner = owner.owner; - statistic.status = 'Draft'; + statistic.status = EntityStatus.DRAFT; const row = await DatabaseServer.createStatistic(statistic); return new MessageResponse(row); } catch (error) { @@ -49,7 +58,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { async (msg: { filters: any, owner: IOwner }) => { try { if (!msg) { - return new MessageError('Invalid load tools parameter'); + return new MessageError('Invalid parameters'); } const { filters, owner } = msg; const { pageIndex, pageSize } = filters; @@ -67,7 +76,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } otherOptions.fields = [ 'id', - // 'creator', + 'creator', 'owner', 'name', 'description', @@ -100,7 +109,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { async (msg: { id: string, owner: IOwner }) => { try { if (!msg) { - return new MessageError('Invalid load tools parameter'); + return new MessageError('Invalid parameters'); } const { id, owner } = msg; const item = await DatabaseServer.getStatisticById(id); @@ -126,7 +135,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { async (msg: { id: string, owner: IOwner }) => { try { if (!msg) { - return new MessageError('Invalid load tools parameter'); + return new MessageError('Invalid parameters'); } const { id, owner } = msg; const item = await DatabaseServer.getStatisticById(id); @@ -158,10 +167,10 @@ export async function statisticsAPI(logger: PinoLogger): Promise { * @returns {Theme} theme */ ApiResponse(MessageAPI.UPDATE_STATISTIC, - async (msg: { id: string, statistic: any, owner: IOwner }) => { + async (msg: { id: string, statistic: PolicyStatistic, owner: IOwner }) => { try { if (!msg) { - return new MessageError('Invalid update theme parameters'); + return new MessageError('Invalid parameters'); } const { id, statistic, owner } = msg; @@ -172,6 +181,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { item.name = statistic.name; item.description = statistic.description; + item.method = statistic.method; item.config = statistic.config; const result = await DatabaseServer.updateStatistic(item); @@ -193,7 +203,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { async (msg: { id: string, owner: IOwner }) => { try { if (!msg) { - return new MessageError('Invalid delete theme parameters'); + return new MessageError('Invalid parameters'); } const { id, owner } = msg; const item = await DatabaseServer.getStatisticById(id); @@ -208,5 +218,36 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } }); + /** + * Publish statistic + * + * @param {any} msg - statistic id + * + * @returns {boolean} - Operation success + */ + ApiResponse(MessageAPI.PUBLISH_STATISTIC, + async (msg: { id: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters'); + } + const { id, owner } = msg; + + const item = await DatabaseServer.getStatisticById(id); + if (!item || item.owner !== owner.owner) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + throw new Error(`Item already published`); + } + item.status = EntityStatus.PUBLISHED; + + const result = await DatabaseServer.updateStatistic(item); + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); } diff --git a/interfaces/src/type/entity-status.type.ts b/interfaces/src/type/entity-status.type.ts new file mode 100644 index 0000000000..83011777e7 --- /dev/null +++ b/interfaces/src/type/entity-status.type.ts @@ -0,0 +1,8 @@ +/** + * Entity status + */ +export enum EntityStatus { + DRAFT = 'DRAFT', + PUBLISHED = 'PUBLISHED', + ERROR = 'ERROR' +} diff --git a/interfaces/src/type/index.ts b/interfaces/src/type/index.ts index c7f6586e77..70b5f1fecb 100644 --- a/interfaces/src/type/index.ts +++ b/interfaces/src/type/index.ts @@ -45,3 +45,4 @@ export * from './assigned-entity.type.js'; export * from './access.type.js'; export * from './pino-log.type.js'; export * from './policy-test-status.type.js'; +export * from './entity-status.type.js'; \ 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 31c3a64a80..1e4c2e6a77 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -223,6 +223,7 @@ export enum MessageAPI { GET_STATISTIC_RELATIONSHIPS = 'GET_STATISTIC_RELATIONSHIPS', UPDATE_STATISTIC = 'UPDATE_STATISTIC', DELETE_STATISTIC = 'DELETE_STATISTIC', + PUBLISH_STATISTIC = 'PUBLISH_STATISTIC' } /** From c54dc4f9293eddecb4d9a681402bb4f8cba18044 Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Wed, 18 Sep 2024 14:48:19 +0000 Subject: [PATCH 20/48] [skip ci] Add swagger.yaml --- swagger.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/swagger.yaml b/swagger.yaml index 5251e096bd..0546f55dff 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -15555,7 +15555,11 @@ components: example: 0.0.1 status: type: string - example: Draft + enum: + - DRAFT + - PUBLISHED + - ERROR + example: DRAFT method: type: string example: '' From bb040e9c4ed94e9a750b91a634c54a8e6a854740 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 23 Sep 2024 18:05:06 +0400 Subject: [PATCH 21/48] update Signed-off-by: Stepan Kiryakov --- .../score-dialog/score-dialog.component.html | 99 +++++++++ .../score-dialog/score-dialog.component.scss | 80 +++++++ .../score-dialog/score-dialog.component.ts | 46 ++++ .../lang-modes/formula-lang.mode.ts | 4 +- .../models/schema-formulas.ts | 104 +++++++++ .../models/schema-node.ts | 85 ++++++++ .../models/schema-scores.ts | 120 +++++++++++ .../schema-variables.ts} | 199 +----------------- ...cy-statistics-configuration.component.html | 114 +++++++++- ...cy-statistics-configuration.component.scss | 73 ++++++- ...licy-statistics-configuration.component.ts | 91 +++++--- .../policy-statistics.module.ts | 4 + frontend/src/app/themes/guardian/button.scss | 4 +- frontend/src/app/themes/guardian/grid.scss | 60 +++++- frontend/src/app/themes/guardian/index.scss | 1 + frontend/src/app/themes/guardian/input.scss | 1 + .../src/app/themes/guardian/multi-select.scss | 151 +++++++++++++ 17 files changed, 1008 insertions(+), 228 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-node.ts create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts rename frontend/src/app/modules/policy-statistics/policy-statistics-configuration/{schema-node.ts => models/schema-variables.ts} (56%) create mode 100644 frontend/src/app/themes/guardian/multi-select.scss diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html new file mode 100644 index 0000000000..b6e5fcbb44 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.html @@ -0,0 +1,99 @@ +
+
+
Options
+
{{score.id}}
+
+
+ +
+
+
+
+
+
+
+ +
+ + +
+ +
Options
+ +
+
+
Description
+
Value
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+ No Options +
+
+
+ +
+ + +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss new file mode 100644 index 0000000000..2ed5b49228 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss @@ -0,0 +1,80 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: column; + 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 { + margin-bottom: 24px; +} + +.dialog-grid-body { + max-height: 256px; + overflow: auto; +} + +.dialog-body { + height: auto; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.option-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } +} + +.grid-label { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + margin-bottom: 6px; +} + +.col-button { + .guardian-icon-button { + position: relative; + left: 9px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts new file mode 100644 index 0000000000..bce9f6d794 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'score-dialog', + templateUrl: './score-dialog.component.html', + styleUrls: ['./score-dialog.component.scss'], +}) +export class ScoreDialog { + public loading = true; + public score: any; + public options: any[]; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.score = this.config.data?.score || {}; + this.options = this.score.options || []; + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public deleteOption(item: any, $event: any) { + + } + + public addOption() { + this.options.push({}); + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit(): void { + this.ref.close(this.score); + } +} diff --git a/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts index b09c2a16ef..3ede5360a1 100644 --- a/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts +++ b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts @@ -8,10 +8,10 @@ import CodeMirror, { Mode, StringStream } from 'codemirror'; CodeMirror.defineMode('formula-lang', function (config, parserConfig) { const variables = (config as any).variables as string[]; const variablesName = variables.map((v) => `(${v})`).join('|'); - const isVariables = new RegExp(variablesName); + const isVariables = variables.length ? new RegExp(variablesName) : null; const policySyntaxOverlay: Mode = { token: function (stream: StringStream) { - if (stream.match(isVariables)) { + if (isVariables && stream.match(isVariables)) { return 'formula-variable'; } else if(stream.match(/([a-zA-Z]+)\(/, false)) { stream.next(); diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts new file mode 100644 index 0000000000..8c6f75bd8d --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts @@ -0,0 +1,104 @@ +export interface IFormulaData { + id: string; + type: string; + description: string; + formula: string; +} + +export class SchemaFormula implements IFormulaData { + public id: string; + public type: string; + public description: string; + public formula: string; + public index: number; + + constructor() { + this.type = 'string'; + } + + public getJson(): IFormulaData { + return { + id: this.id, + type: this.type, + description: this.description, + formula: this.formula + } + } + + public static fromData(data: IFormulaData): SchemaFormula { + const formula = new SchemaFormula(); + formula.id = data.id; + formula.type = data.type || 'string'; + formula.description = data.description; + formula.formula = data.formula; + return formula; + } +} + +export class SchemaFormulas { + private readonly symbol = 'C'; + private startIndex: number = 1; + + public formulas: SchemaFormula[]; + public names: Set; + + constructor() { + this.formulas = []; + this.names = new Set(); + } + + public setDefault() { + this.names.clear(); + this.startIndex = 1; + this.add(); + } + + public getName(): string { + let name: string = ''; + for (let index = this.startIndex; index < 1000000; index++) { + name = `${this.symbol}${index}`; + if (!this.names.has(name)) { + this.names.add(name); + return name; + } + } + return name; + } + + public add() { + const formula = new SchemaFormula(); + formula.id = this.getName(); + formula.description = ''; + formula.formula = ''; + this.formulas.push(formula); + } + public delete(formula: SchemaFormula) { + this.formulas = this.formulas.filter((f) => f !== formula); + if (this.formulas.length === 0) { + this.setDefault(); + } + } + + public fromData(data: IFormulaData[]) { + this.formulas = []; + if (data) { + for (let index = 0; index < data.length; index++) { + const item = data[index]; + const formula = SchemaFormula.fromData(item); + formula.index = index; + this.formulas.push(formula); + } + } + for (const item of this.formulas) { + this.names.add(item.id); + } + this.startIndex = this.formulas.length + 1; + if (this.formulas.length === 0) { + this.setDefault(); + } + } + + public getJson(): any[] { + return this.formulas.map((f) => f.getJson()); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-node.ts new file mode 100644 index 0000000000..490abd5602 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-node.ts @@ -0,0 +1,85 @@ +import { Schema } from "@guardian/interfaces"; +import { TreeListView, TreeListData, TreeListItem } from "../../tree-graph/tree-list"; +import { TreeNode } from "../../tree-graph/tree-node"; + +export interface SchemaData { + iri: string; + name: string; + description: string; +} + +export interface FieldData { + name: string; + type: string; + description: string; + property: string; + propertyName: string; + isArray: boolean; + isRef: boolean; +} + +export class SchemaRules { + public relationships: 'main' | 'related' | 'unrelated'; + public unique: 'true' | 'false'; + + constructor() { + this.relationships = 'unrelated'; + this.unique = 'false'; + } +} + +export class SchemaNode extends TreeNode { + public fields: TreeListView; + public rules: SchemaRules; + + public override clone(): SchemaNode { + const clone = new SchemaNode(this.id, this.type, this.data); + clone.type = this.type; + clone.data = this.data; + clone.childIds = new Set(this.childIds); + clone.fields = this.fields; + clone.rules = this.rules; + return clone; + } + + public override update() { + if (this.parent) { + const root = this.getRoot() as SchemaNode; + const parentFields = root.fields; + this.fields = parentFields.createView((s) => { + return s.parent?.data?.type === this.data.iri; + }); + } + this.fields.setSelectedLimit(4); + this.fields.updateSearch(); + } + + public static from(schema: Schema, properties: Map): SchemaNode { + const id = schema.iri; + const type = schema.entity === 'VC' ? 'root' : 'sub'; + const data = { + iri: schema.iri || '', + name: schema.name || '', + description: schema.description || '' + }; + const result = new SchemaNode(id, type, data); + const fields = TreeListData.fromObject(schema, 'fields', (f) => { + if (f.data.property) { + f.data.propertyName = properties.get(f.data.property) || f.data.property; + } + return f; + }); + result.fields = TreeListView.createView(fields, (s) => { + return !s.parent; + }); + result.fields.setSearchRules((item) => { + return [ + `(${item.description || ''})|(${item.propertyName || ''})`.toLocaleLowerCase(), + `(${item.description || ''})`.toLocaleLowerCase(), + `(${item.propertyName || ''})`.toLocaleLowerCase() + ]; + }) + result.rules = new SchemaRules(); + return result; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts new file mode 100644 index 0000000000..54382996ca --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts @@ -0,0 +1,120 @@ +export interface IScoreOption { + description: string; + value: number; +} + +export interface IScoreData { + id: string; + type: string; + description: string; + relationships: string[]; + options: IScoreOption[]; +} + +export class SchemaScore implements IScoreData { + public id: string; + public type: string; + public description: string; + public relationships: string[]; + public options: IScoreOption[]; + public index: number; + + constructor() { + this.type = 'string'; + } + + public getJson(): IScoreData { + return { + id: this.id, + type: this.type, + description: this.description, + relationships: this.relationships, + options: this.options + } + } + + public static fromData(data: IScoreData): SchemaScore { + const formula = new SchemaScore(); + formula.id = data.id; + formula.type = data.type || 'string'; + formula.description = data.description; + formula.relationships = data.relationships; + formula.options = (data.options || []).slice(); + return formula; + } + + public add() { + this.options.push({ + description: '', + value: 0 + }); + } + public delete(option: IScoreOption) { + this.options = this.options.filter((o) => o !== option); + } +} + +export class SchemaScores { + private readonly symbol = 'B'; + private startIndex: number = 1; + + public scores: SchemaScore[]; + public names: Set; + + constructor() { + this.scores = []; + this.names = new Set(); + } + + public setDefault() { + this.names.clear(); + this.startIndex = 1; + } + + public getName(): string { + let name: string = ''; + for (let index = this.startIndex; index < 1000000; index++) { + name = `${this.symbol}${index}`; + if (!this.names.has(name)) { + this.names.add(name); + return name; + } + } + return name; + } + + public add() { + const score = new SchemaScore(); + score.id = this.getName(); + this.scores.push(score); + } + public delete(score: SchemaScore) { + this.scores = this.scores.filter((f) => f !== score); + if (this.scores.length === 0) { + this.setDefault(); + } + } + + public fromData(data: IScoreData[]) { + this.scores = []; + if (data) { + for (let index = 0; index < data.length; index++) { + const item = data[index]; + const score = SchemaScore.fromData(item); + score.index = index; + this.scores.push(score); + } + } + for (const item of this.scores) { + this.names.add(item.id); + } + this.startIndex = this.scores.length + 1; + if (this.scores.length === 0) { + this.setDefault(); + } + } + + public getJson(): any[] { + return this.scores.map((f) => f.getJson()); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts similarity index 56% rename from frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts rename to frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts index 903da05a14..c11c976228 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/schema-node.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts @@ -1,90 +1,8 @@ import { Schema } from "@guardian/interfaces"; -import { TreeListView, TreeListData, TreeListItem } from "../tree-graph/tree-list"; -import { TreeNode } from "../tree-graph/tree-node"; +import { FieldData, SchemaNode } from "./schema-node"; +import { TreeListItem } from "../../tree-graph/tree-list"; -export interface SchemaData { - iri: string; - name: string; - description: string; -} - -export interface FieldData { - name: string; - type: string; - description: string; - property: string; - propertyName: string; - isArray: boolean; - isRef: boolean; -} - -export class SchemaRules { - public relationships: 'main' | 'related' | 'unrelated'; - public unique: 'true' | 'false'; - - constructor() { - this.relationships = 'unrelated'; - this.unique = 'false'; - } -} - -export class SchemaNode extends TreeNode { - public fields: TreeListView; - public rules: SchemaRules; - - public override clone(): SchemaNode { - const clone = new SchemaNode(this.id, this.type, this.data); - clone.type = this.type; - clone.data = this.data; - clone.childIds = new Set(this.childIds); - clone.fields = this.fields; - clone.rules = this.rules; - return clone; - } - - public override update() { - if (this.parent) { - const root = this.getRoot() as SchemaNode; - const parentFields = root.fields; - this.fields = parentFields.createView((s) => { - return s.parent?.data?.type === this.data.iri; - }); - } - this.fields.setSelectedLimit(4); - this.fields.updateSearch(); - } - - public static from(schema: Schema, properties: Map): SchemaNode { - const id = schema.iri; - const type = schema.entity === 'VC' ? 'root' : 'sub'; - const data = { - iri: schema.iri || '', - name: schema.name || '', - description: schema.description || '' - }; - const result = new SchemaNode(id, type, data); - const fields = TreeListData.fromObject(schema, 'fields', (f) => { - if (f.data.property) { - f.data.propertyName = properties.get(f.data.property) || f.data.property; - } - return f; - }); - result.fields = TreeListView.createView(fields, (s) => { - return !s.parent; - }); - result.fields.setSearchRules((item) => { - return [ - `(${item.description || ''})|(${item.propertyName || ''})`.toLocaleLowerCase(), - `(${item.description || ''})`.toLocaleLowerCase(), - `(${item.propertyName || ''})`.toLocaleLowerCase() - ]; - }) - result.rules = new SchemaRules(); - return result; - } -} - -interface IVariableData { +export interface IVariableData { id: string; schemaId: string; path: string; @@ -182,7 +100,7 @@ export class SchemaVariable implements IVariableData { } export class SchemaVariables { - private readonly symbol = 'A' + private readonly symbol = 'A'; private startIndex: number = 1; public variables: SchemaVariable[]; @@ -193,6 +111,10 @@ export class SchemaVariables { this.names = new Set(); } + public get(id: string): SchemaVariable | undefined { + return this.variables.find((v) => v.id === id); + } + public getName(): string { let name: string = ''; for (let index = this.startIndex; index < 1000000; index++) { @@ -295,113 +217,10 @@ export class SchemaVariables { public updateType(schemas: Schema[]) { const map = new Map(); for (const schema of schemas) { - map.set(schema.iri, schema) + map.set(schema.iri, schema); } for (const variable of this.variables) { variable.updateType(map); } } } - -interface IFormulaData { - id: string; - type: string; - description: string; - formula: string; -} - -export class SchemaFormula implements IFormulaData { - public id: string; - public type: string; - public description: string; - public formula: string; - public index: number; - - constructor() { - this.type = 'string'; - } - - public getJson(): IFormulaData { - return { - id: this.id, - type: this.type, - description: this.description, - formula: this.formula - } - } - - public static fromData(data: IFormulaData): SchemaFormula { - const formula = new SchemaFormula(); - formula.id = data.id; - formula.type = data.type || 'string'; - formula.description = data.description; - formula.formula = data.formula; - return formula; - } -} - -export class SchemaFormulas { - private readonly symbol = 'B' - private startIndex: number = 1; - - public formulas: SchemaFormula[]; - public names: Set; - - constructor() { - this.formulas = []; - this.names = new Set(); - } - - public setDefault() { - this.names.clear(); - this.startIndex = 1; - this.add(); - } - - public getName(): string { - let name: string = ''; - for (let index = this.startIndex; index < 1000000; index++) { - name = `${this.symbol}${index}`; - if (!this.names.has(name)) { - this.names.add(name); - return name; - } - } - return name; - } - - public add() { - const formula = new SchemaFormula(); - formula.id = this.getName(); - this.formulas.push(formula); - } - public delete(formula: SchemaFormula) { - this.formulas = this.formulas.filter((f) => f !== formula); - if (this.formulas.length === 0) { - this.setDefault() - } - } - - public fromData(data: IFormulaData[]) { - this.formulas = []; - if (data) { - for (let index = 0; index < data.length; index++) { - const item = data[index]; - const formula = SchemaFormula.fromData(item); - formula.index = index; - this.formulas.push(formula); - } - } - for (const item of this.formulas) { - this.names.add(item.id); - } - this.startIndex = this.formulas.length + 1; - if (this.formulas.length === 0) { - this.setDefault() - } - } - - public getJson(): any[] { - return this.formulas.map((f) => f.getJson()); - } -} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 7cbdb1b63a..8028f9e105 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -83,7 +83,7 @@
-
+
@@ -413,7 +413,7 @@
- Input + Input Fields
@@ -457,7 +457,111 @@
- Output + Scores +
+
+
+
ID
+ +
DESCRIPTION
+
RELATIONSHIPS
+
OPTIONS
+
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+ + +
+ {{ getRelationshipsName(option) }} +
+
+ +
{{ option.id }} - {{ option.fieldDescription }}
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ There were no fields selected yet +
+
+ +
+
+
+
+ Output Fields
@@ -513,7 +617,7 @@
+
+ +
+ {{item?.name || title}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss new file mode 100644 index 0000000000..815784bb66 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss @@ -0,0 +1,62 @@ +.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; + + .guardian-user-back-button { + button { + border-color: #fff; + } + } + + .guardian-user-page-header { + color: #fff; + } + + + .policy-name { + color: #fff; + 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: center; + padding: 12px 48px; + background: #fff; + border-top: 1px solid #E1E7EF; + position: relative; + + button { + position: absolute; + height: 40px; + top: 12px; + right: 48px; + width: 135px; + } + + .guardian-step-container { + height: 40px; + width: 620px; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts new file mode 100644 index 0000000000..23731b46a9 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts @@ -0,0 +1,103 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { 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'; + +@Component({ + selector: 'app-policy-report-configuration', + templateUrl: './policy-report-configuration.component.html', + styleUrls: ['./policy-report-configuration.component.scss'], +}) +export class PolicyReportsConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public id: string; + public item: any; + public policy: any; + public schemas: Schema[]; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private schemaService: SchemaService, + 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.id = this.route.snapshot.params['id']; + this.loading = true; + forkJoin([ + this.policyStatisticsService.getItem(this.id), + this.policyStatisticsService.getRelationships(this.id), + this.schemaService.properties() + ]).subscribe(([item, relationships, properties]) => { + this.item = item; + this.policy = relationships?.policy || {}; + const schemas = relationships?.schemas || []; + this.schemas = []; + for (const schema of schemas) { + try { + this.schemas.push(new Schema(schema)); + } catch (error) { + console.log(error); + } + } + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-statistics']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index 8028f9e105..f289709874 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -514,7 +514,11 @@
- +
+
+ {{option.description}} +
+
+ + div { + &>div { border-right: 1px solid var(--guardian-border-color); &:last-child { @@ -154,6 +107,7 @@ bottom: 0px; border: 1px solid var(--guardian-primary-color, #4169E2); } + .p-inputwrapper-focus+.cell-focus, .guardian-input:focus+.cell-focus { display: block; @@ -269,6 +223,7 @@ &:first-child { padding-left: 24px; } + &:last-child { padding-left: 24px; } @@ -285,6 +240,7 @@ &:first-child { padding-left: 24px; } + &:last-child { padding-left: 24px; } @@ -295,4 +251,16 @@ border-top-right-radius: 8px; } } + + @each $name, $size in $sizes { + .col-#{$name} { + width: $size; + min-width: $size; + max-width: $size; + } + } + + .col-auto { + width: 100%; + } } \ No newline at end of file From a4bbadef0a64187c9a45829533be38f7697ca9fe Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 25 Sep 2024 17:10:57 +0400 Subject: [PATCH 23/48] update Signed-off-by: Stepan Kiryakov --- .../policy-test-dialog.component.html | 2 +- .../score-dialog/score-dialog.component.html | 11 +- .../score-dialog/score-dialog.component.scss | 2 +- ...policy-report-configuration.component.html | 195 +++++++++++++++++- ...policy-report-configuration.component.scss | 169 ++++++++++++++- .../policy-report-configuration.component.ts | 173 +++++++++++++++- .../models/data.ts | 48 +++++ .../models/schema-formulas.ts | 7 +- .../models/schema-scores.ts | 13 +- .../models/schema-variables.ts | 15 +- ...cy-statistics-configuration.component.html | 18 +- ...cy-statistics-configuration.component.scss | 4 +- ...licy-statistics-configuration.component.ts | 12 +- .../src/app/themes/guardian/checkbox.scss | 5 + frontend/src/app/themes/guardian/dialog.scss | 2 + frontend/src/app/themes/guardian/grid.scss | 19 +- .../src/app/themes/guardian/separator.scss | 60 +++++- .../src/app/themes/guardian/variables.scss | 1 + .../new-header/new-header.component.html | 6 +- frontend/src/assets/images/icons/16/list.svg | 3 + frontend/src/assets/images/icons/list.svg | 4 + 21 files changed, 702 insertions(+), 67 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/data.ts create mode 100644 frontend/src/assets/images/icons/16/list.svg create mode 100644 frontend/src/assets/images/icons/list.svg diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-test-dialog/policy-test-dialog.component.html b/frontend/src/app/modules/policy-engine/dialogs/policy-test-dialog/policy-test-dialog.component.html index ecf7b6d945..57ad68e4a8 100644 --- a/frontend/src/app/modules/policy-engine/dialogs/policy-test-dialog/policy-test-dialog.component.html +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-test-dialog/policy-test-dialog.component.html @@ -26,7 +26,7 @@
-
+
-
+
- No Options +
+ + +
+
There were no options created yet.
diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss index 2ed5b49228..2c05581556 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss +++ b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.scss @@ -69,7 +69,7 @@ form { font-weight: 500; line-height: 14px; text-align: left; - margin-bottom: 6px; + margin-bottom: 16px; } .col-button { diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html index 50f6ed27e4..e2ca833b4a 100644 --- a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html @@ -31,5 +31,198 @@
- + +
+ + + + + + +
+
+ + + + + +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss index 815784bb66..20f98b2657 100644 --- a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.scss @@ -2,6 +2,7 @@ position: relative; padding: 0px; user-select: none; + background: var(--guardian-grey-background, #F9FAFC); .header-container { padding: 56px 48px 10px 48px; @@ -40,23 +41,177 @@ height: 64px; width: 100%; display: flex; - justify-content: center; + justify-content: flex-end; padding: 12px 48px; background: #fff; border-top: 1px solid #E1E7EF; position: relative; button { - position: absolute; height: 40px; - top: 12px; - right: 48px; width: 135px; + margin-left: 16px; } + } - .guardian-step-container { - height: 40px; - width: 620px; + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + + .nav-container { + display: flex; + width: 320px; + height: 100%; + padding: 24px 8px 24px 48px; + pointer-events: none; + + .guardian-step-container { + width: 264px; + height: 337px; + padding: 8px 0px 8px 0px; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px #00000014; + background: #fff; + + .guardian-step { + padding-left: 24px; + } + + .guardian-step-separator { + margin-left: 16px; + } + } + } + + .nav-body-container { + display: flex; + width: calc(100% - 320px); + height: 100%; + padding: 24px 48px 24px 8px; + overflow: auto; + + .step-body-container { + display: flex; + width: 100%; + min-height: 100px; + height: fit-content; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px #00000014; + background: #fff; + padding: 24px; + flex-direction: column; + margin-bottom: 16px; + + .step-body-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: #181818; + margin-bottom: 24px; + } + } + } + + .dialog-grid-row { + cursor: pointer; + + &:hover { + background: #f0f3fc; + } + + * { + pointer-events: none; + } + } + + .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: #181818; + padding: 8px 0px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #23252E; + width: 100%; + } + } + } + + .scores-container { + .score-container { + border: 1px solid #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: #000000; + 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: #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; + } + } } } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts index 23731b46a9..19a0d59ad4 100644 --- a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.ts @@ -1,11 +1,40 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Schema, UserPermissions } from '@guardian/interfaces'; +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 { SchemaService } from 'src/app/services/schema.service'; import { DialogService } from 'primeng/dynamicdialog'; +import { IStatistic } from '../policy-statistics-configuration/models/data'; +import { FormGroup } from '@angular/forms'; + +interface IOption { + id: string; + description: string; + value: any; +} + +interface IVariable { + id: string; + description: string; + value: any; +} + +interface IScore { + id: string; + description: string; + value: any; + relationships: IVariable[]; + options: IOption[] +} + +interface IFormula { + id: string; + description: string; + value: any; + formula: string; +} @Component({ selector: 'app-policy-report-configuration', @@ -20,9 +49,17 @@ export class PolicyReportsConfigurationComponent implements OnInit { public user: UserPermissions = new UserPermissions(); public owner: string; public id: string; - public item: any; + public item: IStatistic; public policy: any; public schemas: Schema[]; + public stepper = [true, false, false, false]; + public stepIndex = 0; + public documents: any[]; + public document: any; + public preview: IVariable[]; + public scores: IScore[]; + public formulas: IFormula[]; + public scoresValid: boolean = false; private subscription = new Subscription(); @@ -89,6 +126,9 @@ export class PolicyReportsConfigurationComponent implements OnInit { console.log(error); } } + this.documents = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; + this.document = null; + this.update(); setTimeout(() => { this.loading = false; }, 1000); @@ -97,7 +137,136 @@ export class PolicyReportsConfigurationComponent implements OnInit { }); } + private update() { + const config = this.item.config || {}; + const variables = config.variables || []; + const formulas = config.formulas || []; + const scores = config.scores || []; + const preview = new Map(); + + this.preview = []; + this.scores = []; + this.formulas = []; + + for (const variable of variables) { + const field: IVariable = { + id: variable.id, + description: variable.fieldDescription, + value: this.getFieldValue(this.document, variable) + } + 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.value + }); + } + } + this.scores.push({ + id: score.id, + description: score.description, + value: undefined, + relationships, + options + }); + } + + for (const formula of formulas) { + this.formulas.push({ + id: formula.id, + description: formula.description, + value: undefined, + formula: formula.formula + }); + } + + this.updateScore(); + } + public onBack() { this.router.navigate(['/policy-statistics']); } + + public onStep(index: number) { + this.stepIndex = index; + this.stepIndex = Math.min(Math.max(this.stepIndex, 0), 3); + this.stepper.fill(false); + this.stepper[this.stepIndex] = true; + } + + public onPrev() { + this.onStep(this.stepIndex - 1); + } + + private getFieldValue(document: any, variable: any): string { + return 'test'; + } + + public onNextStep1() { + this.onStep(1); + } + + public onNextStep2() { + this.onStep(2); + } + + public onNextStep3() { + this.onStep(3); + this.calculate(); + } + + public onCreate() { + + } + + public onSelectDocument(item: any) { + this.document = item; + } + + public updateScore() { + for (const score of this.scores) { + if(!score.value) { + this.scoresValid = false; + return; + } + } + this.scoresValid = true; + } + + private calculate() { + const result: any = {}; + + for (const field of this.preview) { + result[field.id] = field.value; + } + + for (const score of this.scores) { + result[score.id] = score.value; + } + + for (const formula of this.formulas) { + formula.value = this.calcFormula(formula, result); + result[formula.id] = formula.value; + } + } + + private calcFormula(formula: IFormula, state: any) { + return formula.formula; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/data.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/data.ts new file mode 100644 index 0000000000..6a3a687b98 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/data.ts @@ -0,0 +1,48 @@ +export interface IFormulaData { + id: string; + type: string; + description: string; + formula: string; +} + +export interface IVariableData { + id: string; + schemaId: string; + path: string; + schemaName: string; + schemaPath: string; + fieldType: string; + fieldRef: boolean; + fieldArray: boolean; + fieldDescription: string; + fieldProperty: string; + fieldPropertyName: string; +} + +export interface IScoreOption { + description: string; + value: number; +} + +export interface IScoreData { + id: string; + type: string; + description: string; + relationships: string[]; + options: IScoreOption[]; +} + +export interface IStatistic { + id: string; + name: string; + description: string; + instanceTopicId: string; + policyId: string; + owner: string; + status: string; + config: { + variables: IVariableData[], + scores: IScoreData[], + formulas: IFormulaData[], + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts index 8c6f75bd8d..dec058a2e0 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-formulas.ts @@ -1,9 +1,4 @@ -export interface IFormulaData { - id: string; - type: string; - description: string; - formula: string; -} +import { IFormulaData } from "./data"; export class SchemaFormula implements IFormulaData { public id: string; diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts index 54382996ca..11ec2c3928 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-scores.ts @@ -1,15 +1,4 @@ -export interface IScoreOption { - description: string; - value: number; -} - -export interface IScoreData { - id: string; - type: string; - description: string; - relationships: string[]; - options: IScoreOption[]; -} +import { IScoreData, IScoreOption } from "./data"; export class SchemaScore implements IScoreData { public id: string; diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts index c11c976228..d45ceb1ef2 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/models/schema-variables.ts @@ -1,20 +1,7 @@ import { Schema } from "@guardian/interfaces"; import { FieldData, SchemaNode } from "./schema-node"; import { TreeListItem } from "../../tree-graph/tree-list"; - -export interface IVariableData { - id: string; - schemaId: string; - path: string; - schemaName: string; - schemaPath: string; - fieldType: string; - fieldRef: boolean; - fieldArray: boolean; - fieldDescription: string; - fieldProperty: string; - fieldPropertyName: string; -} +import { IVariableData } from "./data"; export class SchemaVariable implements IVariableData { public id: string; diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html index f289709874..8e792ca894 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-statistics-configuration/policy-statistics-configuration.component.html @@ -452,7 +452,14 @@
- There were no fields selected yet +
+ + +
+
There were no fields selected yet.
@@ -546,7 +553,14 @@
- There were no fields selected yet +
+ + +
+
There were no scores created yet.
diff --git a/frontend/src/assets/images/icons/16/list.svg b/frontend/src/assets/images/icons/16/list.svg new file mode 100644 index 0000000000..fb6b0627b5 --- /dev/null +++ b/frontend/src/assets/images/icons/16/list.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/list.svg b/frontend/src/assets/images/icons/list.svg new file mode 100644 index 0000000000..48d0c142dc --- /dev/null +++ b/frontend/src/assets/images/icons/list.svg @@ -0,0 +1,4 @@ + + + + From 9d5a28f94e83acdd9811575dc88443671a41b28b Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Thu, 26 Sep 2024 19:08:43 +0400 Subject: [PATCH 24/48] update Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 77 +++++++- api-gateway/src/helpers/guardians.ts | 19 ++ .../validation/schemas/accounts.ts | 57 ------ .../validation/schemas/document.dto.ts | 164 +++++++++++++++++- .../validation/schemas/documents.ts | 35 ---- .../middlewares/validation/schemas/index.ts | 1 - .../src/database-modules/database-server.ts | 13 +- ...policy-report-configuration.component.html | 127 +++++++++++--- ...policy-report-configuration.component.scss | 53 +++++- .../policy-report-configuration.component.ts | 140 +++++++++++++-- .../policy-statistics.module.ts | 2 + .../policy-statistics.component.html | 1 - .../app/services/policy-statistics.service.ts | 9 + frontend/src/app/themes/guardian/grid.scss | 77 +++++++- frontend/src/app/themes/guardian/index.scss | 2 + frontend/src/app/themes/guardian/menu.scss | 15 ++ frontend/src/app/themes/guardian/scroll.scss | 48 +++++ .../src/api/statistics.service.ts | 62 +++++++ .../src/type/messages/message-api.type.ts | 3 +- 19 files changed, 764 insertions(+), 141 deletions(-) delete mode 100644 api-gateway/src/middlewares/validation/schemas/documents.ts create mode 100644 frontend/src/app/themes/guardian/menu.scss create mode 100644 frontend/src/app/themes/guardian/scroll.scss diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index 271462115c..6a0cf20e4a 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -2,7 +2,7 @@ import { IAuthUser, PinoLogger } from '@guardian/common'; import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Req, Response } from '@nestjs/common'; import { Permissions } from '@guardian/interfaces'; import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; -import { Examples, InternalServerErrorDTO, StatisticsDTO, pageHeader } from '#middlewares'; +import { Examples, InternalServerErrorDTO, StatisticsDTO, VcDocumentDTO, pageHeader } from '#middlewares'; import { UseCache, Guardians, InternalException, ONLY_SR, EntityOwner, CacheService } from '#helpers'; import { AuthUser, Auth } from '#auth'; @@ -99,10 +99,7 @@ export class PolicyStatisticsApi { try { const owner = new EntityOwner(user); const guardians = new Guardians(); - const { items, count } = await guardians.getStatistics({ - pageIndex, - pageSize - }, owner); + const { items, count } = await guardians.getStatistics({ pageIndex, pageSize }, owner); return res.header('X-Total-Count', count).send(items); } catch (error) { await InternalException(error, this.logger); @@ -194,6 +191,76 @@ export class PolicyStatisticsApi { } } + /** + * Get page + */ + @Get('/:id/documents') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.' + ONLY_SR, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Statistic ID', + 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(StatisticsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getDocuments( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Param('id') id: 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.getStatisticDocuments(id, owner, pageIndex, pageSize); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + + + + + + + + + + + /** * Update statistic */ diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 87f10dc12d..e94260ff23 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -2880,6 +2880,25 @@ export class Guardians extends NatsService { return await this.sendMessage(MessageAPI.GET_STATISTIC_RELATIONSHIPS, { id, owner }); } + /** + * Return documents + * + * @param id + * @param owner + * @param pageIndex + * @param pageSize + * + * @returns {ResponseAndCount} + */ + public async getStatisticDocuments( + id: string, + owner: IOwner, + pageIndex?: number, + pageSize?: number + ): Promise> { + return await this.sendMessage(MessageAPI.GET_STATISTIC_DOCUMENTS, { id, owner, pageIndex, pageSize }); + } + /** * Update statistic * @param id diff --git a/api-gateway/src/middlewares/validation/schemas/accounts.ts b/api-gateway/src/middlewares/validation/schemas/accounts.ts index 7d4900a175..2f111cdd54 100644 --- a/api-gateway/src/middlewares/validation/schemas/accounts.ts +++ b/api-gateway/src/middlewares/validation/schemas/accounts.ts @@ -5,7 +5,6 @@ import { UserRole } from '@guardian/interfaces'; import { Expose } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; import { Match } from '../../../helpers/decorators/match.validator.js'; -import { PolicyDTO } from './policies.dto.js'; export class AccountsResponseDTO { @ApiProperty() @@ -120,62 +119,6 @@ export class CredentialSubjectDTO { type: string; } -export class ProofDTO { - @ApiProperty() - type: string; - - @ApiProperty() - created: Date; - - @ApiProperty() - verificationMethod: string; - - @ApiProperty() - proofPurpose: string; - - @ApiProperty() - jws: string; -} - -export class VcDocumentDTO { - @ApiProperty() - id: string; - - @ApiProperty() - type: string[]; - - @ApiProperty() - issuer: string; - - @ApiProperty() - issuanceDate: Date; - - @ApiProperty() - '@context': string[] - - @ApiProperty() - credentialSubject: CredentialSubjectDTO; - - @ApiProperty() - proof: ProofDTO; -} - -export class AggregatedDTOItem { - @ApiProperty() - did: string; - - @ApiProperty() - hederaAccountId: string; - - @ApiProperty() - vcDocument: VcDocumentDTO; - - @ApiProperty() - policies: PolicyDTO; -} - -export type AggregatedDTO = AggregatedDTOItem[] - class UserDTO { @ApiProperty() username: string; diff --git a/api-gateway/src/middlewares/validation/schemas/document.dto.ts b/api-gateway/src/middlewares/validation/schemas/document.dto.ts index da4964997d..5b12da1887 100644 --- a/api-gateway/src/middlewares/validation/schemas/document.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/document.dto.ts @@ -1,5 +1,60 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; import { Examples } from '../examples.js'; +import { PolicyDTO } from './policies.dto.js'; + +export class ProofDTO { + @ApiProperty() + type: string; + + @ApiProperty() + created: Date; + + @ApiProperty() + verificationMethod: string; + + @ApiProperty() + proofPurpose: string; + + @ApiProperty() + jws: string; +} + +export class VcDTO { + @ApiProperty({ + type: 'string', + example: Examples.UUID, + nullable: true + }) + id?: string; + + @ApiProperty({ + type: 'string', + isArray: true + }) + '@context': string[]; + + @ApiProperty({ + type: 'string', + isArray: true + }) + type: string[]; + + @ApiProperty({ + type: 'object', + isArray: true, + required: true + }) + credentialSubject: any | any[]; + + @ApiProperty({ type: 'object', required: true }) + issuer: any | string; + + @ApiProperty({ type: 'string', required: true }) + issuanceDate: string; + + @ApiProperty({ type: 'object', nullable: true }) + proof?: ProofDTO; +} export class VpDTO { @ApiProperty({ @@ -29,7 +84,7 @@ export class VpDTO { @ApiProperty({ type: 'object', }) - proof?: any; + proof?: ProofDTO; } @ApiExtraModels(VpDTO) @@ -106,4 +161,109 @@ export class VpDocumentDTO { type: () => VpDTO, }) document?: VpDTO[]; -} \ No newline at end of file +} + + +@ApiExtraModels(VcDTO) +export class VcDocumentDTO { + @ApiProperty({ + type: 'string', + example: Examples.DB_ID + }) + id?: string; + + @ApiProperty({ + type: 'string', + example: Examples.DB_ID + }) + policyId?: string; + + @ApiProperty({ + type: 'string', + example: 'hash' + }) + hash?: string; + + @ApiProperty({ + type: 'number', + example: 0 + }) + signature?: number; + + @ApiProperty({ + type: 'string', + enum: [ + 'NEW', + 'ISSUE', + 'REVOKE', + 'SUSPEND', + 'RESUME', + 'FAILED' + ], + example: 'NEW' + }) + status?: string; + + @ApiProperty({ + type: 'string', + example: 'Block tag' + }) + tag?: string; + + @ApiProperty({ + type: 'string', + example: 'Document type' + }) + type?: string; + + @ApiProperty({ + type: 'string', + example: Examples.DATE + }) + createDate?: string; + + @ApiProperty({ + type: 'string', + example: Examples.DATE + }) + updateDate?: string; + + @ApiProperty({ + type: 'string', + example: Examples.DID + }) + owner?: string; + + @ApiProperty({ + type: () => VpDTO, + }) + document?: VcDTO; +} + +@ApiExtraModels(VcDTO) +export class ExternalDocumentDTO { + @ApiProperty({ required: true }) + owner: string; + + @ApiProperty({ required: true }) + policyTag: string; + + @ApiProperty({ nullable: false, required: true, type: () => VcDTO }) + document: VcDTO; +} + +export class AggregatedDTOItem { + @ApiProperty() + did: string; + + @ApiProperty() + hederaAccountId: string; + + @ApiProperty() + vcDocument: VcDocumentDTO; + + @ApiProperty() + policies: PolicyDTO; +} + +export type AggregatedDTO = AggregatedDTOItem[] \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/documents.ts b/api-gateway/src/middlewares/validation/schemas/documents.ts deleted file mode 100644 index 7d6fc7c5f2..0000000000 --- a/api-gateway/src/middlewares/validation/schemas/documents.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class VCDocumentDTO { - @ApiProperty({ type: 'string', nullable: false }) - id?: string; - - @ApiProperty({ type: 'string', isArray: true, required: true }) - '@context': string | string[]; - - @ApiProperty({ type: 'string', isArray: true, required: true }) - type: string[]; - - @ApiProperty({ type: 'object', isArray: true, required: true }) - credentialSubject: any | any[]; - - @ApiProperty({ type: 'object', required: true }) - issuer: any | string; - - @ApiProperty({ type: 'string', required: true }) - issuanceDate: string; - - @ApiProperty({ type: 'object', nullable: true }) - proof?: any; -} - -export class ExternalDocumentDTO { - @ApiProperty({ required: true }) - owner: string; - - @ApiProperty({ required: true }) - policyTag: string; - - @ApiProperty({ nullable: false, required: true, type: () => VCDocumentDTO }) - document: VCDocumentDTO; -} \ 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 683715084d..9d1396e989 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -11,7 +11,6 @@ export * from './analytics.js' export * from './demo.js' export * from './profiles.js' export * from './branding.js' -export * from './documents.js' export * from './logs.js' export * from './modules.js' export * from './messages.js' diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index 454ea5e60f..f680ff023b 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -3071,7 +3071,7 @@ export class DatabaseServer extends AbstractDatabaseServer { * @param id */ public static async getContractById(id: string): Promise { - if(!id) return null; + if (!id) return null; return await new DataBaseHelper(ContractCollection).findOne(id); } @@ -3760,4 +3760,15 @@ export class DatabaseServer extends AbstractDatabaseServer { return await new DataBaseHelper(PolicyStatistic).update(row); } + /** + * Get documents + * @param filters + * @param options + */ + public static async getStatisticDocumentsAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[VcDocumentCollection[], number]> { + return await new DataBaseHelper(VcDocumentCollection).findAndCount(filters, options); + } } diff --git a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html index e2ca833b4a..67d809d852 100644 --- a/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/policy-report-configuration/policy-report-configuration.component.html @@ -84,34 +84,109 @@
- {{item?.name || title}} + {{item?.name}}
Policy Name: {{policy.name}} Version: {{policy.version}} diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index 8e25533ade..255399d714 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -7,59 +7,8 @@ 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'; - -interface IOption { - id: string; - description: string; - value: any; -} - -interface IVariable { - id: string; - description: string; - value: any; - schemaId: string; - path: string[]; - fullPath: string[]; - isArray: boolean; -} - -interface IScore { - id: string; - description: string; - value: any; - relationships: IVariable[]; - options: IOption[] -} - -interface IFormula { - id: string; - description: string; - value: any; - formula: string; - type: string; -} - -interface IColumn { - id: string | string[]; - title: string; - type: string; - size: string; - minSize: string; - tooltip: boolean; - permissions?: (user: UserPermissions) => boolean; - canDisplay?: () => boolean; -} - -interface IDocument { - targetDocument: IVCDocument; - relatedDocuments: IVCDocument[]; - unrelatedDocuments: IVCDocument[]; - __id?: string; - __schemaId?: string; - __schemaName?: string; - __cols: Map; -} +import { IDocument, IFormula, IOption, IScore, IVariable } from '../models/assessment'; +import { IColumn } from '../models/grid'; @Component({ selector: 'app-statistic-assessment-configuration', @@ -203,7 +152,6 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { this.policyStatisticsService .createAssessment(this.definitionId, report) .subscribe((assessment) => { - debugger; this.router.navigate([ '/policy-statistics', this.definitionId, diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html index 858a487230..f5f110d015 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html @@ -1,3 +1,173 @@
+
+
+
+
+ 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/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss index e69de29bb2..2d60337228 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss @@ -0,0 +1,188 @@ +.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: #fff; + } + } + + .guardian-user-page-header { + color: #fff; + } + + + .policy-name { + color: #fff; + 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: #fff; + border-bottom: 1px solid #E1E7EF; + position: relative; + box-shadow: 0px 4px 8px 0px #00000014; + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + } + + .nav-body-container { + display: flex; + width: 100%; + height: 100%; + padding: 24px 48px 24px 48px; + overflow: auto; + + .step-body-container { + display: flex; + width: 100%; + min-height: 100px; + height: fit-content; + border-radius: 8px; + box-shadow: 0px 4px 8px 0px #00000014; + background: #fff; + padding: 24px; + flex-direction: column; + margin-bottom: 16px; + + .step-body-header { + font-size: 24px; + font-weight: 600; + line-height: 32px; + text-align: left; + color: #181818; + 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: #181818; + padding: 8px 0px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #23252E; + width: 100%; + min-height: 42px; + } + + .field-value-array { + .field-value { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0px; + } + } + } + } + } + + .scores-container { + .score-container { + border: 1px solid #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: #000000; + 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: #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; + } + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts index 0d6af18fe4..02eb872501 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { EntityStatus, UserPermissions } from '@guardian/interfaces'; +import { GenerateUUIDv4, IStatistic, 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'; -import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; +import { IFormula, IOption, IScore, IVariable } from '../models/assessment'; @Component({ selector: 'app-statistic-assessment-view', @@ -14,12 +14,23 @@ import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dial styleUrls: ['./statistic-assessment-view.component.scss'], }) export class StatisticAssessmentViewComponent implements OnInit { + public readonly title: string = 'Assessment'; + public loading: 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 assessment: any; + public stepper = [true, false, false]; + + public preview: IVariable[]; + public scores: IScore[]; + public formulas: IFormula[]; private subscription = new Subscription(); @@ -77,9 +88,107 @@ export class StatisticAssessmentViewComponent implements OnInit { this.policyStatisticsService.getRelationships(this.definitionId), this.policyStatisticsService.getAssessment(this.definitionId, this.assessmentId), ]).subscribe(([definition, relationships, assessment]) => { + this.definition = definition; + this.policy = relationships?.policy || {}; + this.schemas = relationships?.schemas || []; + this.assessment = assessment || {}; + 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.value + }); + } + } + 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 + }); + } + } + + public onStep(index: number) { + for (let i = 0; i < this.stepper.length; i++) { + this.stepper[i] = false; + } + this.stepper[index] = true; + } } diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html index 858a487230..7f9b3432f3 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.html @@ -1,3 +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/policy-statistics/statistic-assessments/statistic-assessments.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss index e69de29bb2..c46f34e8f2 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss @@ -0,0 +1,23 @@ +.policy-name { + 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/policy-statistics/statistic-assessments/statistic-assessments.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts index 7795b93e25..d9b55f3331 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts @@ -6,7 +6,16 @@ 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'; -import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} @Component({ selector: 'app-statistic-assessments', @@ -14,6 +23,8 @@ import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dial styleUrls: ['./statistic-assessments.component.scss'], }) export class StatisticAssessmentsComponent implements OnInit { + public readonly title: string = 'Assessments'; + public loading: boolean = true; public isConfirmed: boolean = false; public user: UserPermissions = new UserPermissions(); @@ -23,6 +34,10 @@ export class StatisticAssessmentsComponent implements OnInit { 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(); @@ -34,7 +49,43 @@ export class StatisticAssessmentsComponent implements OnInit { 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: 'auto', + tooltip: false + }, { + id: 'target', + title: 'Target', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'messageId', + title: 'Message ID', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: '100', + tooltip: false + }] } ngOnInit() { @@ -54,30 +105,34 @@ export class StatisticAssessmentsComponent implements OnInit { } private loadProfile() { + this.definitionId = this.route.snapshot.params['definitionId']; 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; - }); + 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.definitionId = this.route.snapshot.params['definitionId']; this.loading = true; this.policyStatisticsService .getAssessments( @@ -90,6 +145,10 @@ export class StatisticAssessmentsComponent implements OnInit { 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); @@ -108,4 +167,17 @@ export class StatisticAssessmentsComponent implements OnInit { } 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/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index 3036d4320d..998b98483b 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -24,7 +24,7 @@
- {{item?.name || title}} + {{item?.name}}
Policy Name: {{policy.name}} Version: {{policy.version}} diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts index cae341b0b6..c33fd55e01 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts @@ -7,17 +7,17 @@ import { ProfileService } from 'src/app/services/profile.service'; import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; import { TreeNode } from '../tree-graph/tree-node'; import { TreeListItem } from '../tree-graph/tree-list'; -import { SchemaData, SchemaNode } from './models/schema-node'; -import { SchemaVariables } from "./models/schema-variables"; -import { SchemaFormulas } from "./models/schema-formulas"; +import { SchemaData, SchemaNode } from '../models/schema-node'; +import { SchemaVariables } from "../models/schema-variables"; +import { SchemaFormulas } from "../models/schema-formulas"; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SchemaService } from 'src/app/services/schema.service'; import { TreeSource } from '../tree-graph/tree-source'; import { createAutocomplete } from '../lang-modes/autocomplete'; -import { SchemaScore, SchemaScores } from './models/schema-scores'; +import { SchemaScore, SchemaScores } from '../models/schema-scores'; import { DialogService } from 'primeng/dynamicdialog'; import { ScoreDialog } from '../dialogs/score-dialog/score-dialog.component'; -import { SchemaRule, SchemaRules } from './models/schema-rules'; +import { SchemaRule, SchemaRules } from '../models/schema-rules'; @Component({ selector: 'app-statistic-definition-configuration', diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html index c8e5838924..e60635f49d 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html @@ -174,8 +174,8 @@ class="svg-icon-32" src="/assets/images/icons/32/list.svg" svgClass="icon-color-disabled"> - There were no Policies created yet - Please create new policy to see the data + There were no statistic created yet + Please create new statistic to see the data
diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts index 3ba56b6daa..9747ab69e1 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -91,7 +91,7 @@ export class StatisticDefinitionsComponent implements OnInit { size: 'auto', tooltip: false }, { - id: 'docs', + id: 'documents', title: 'Documents', type: 'text', size: 'auto', diff --git a/frontend/src/app/themes/guardian/page.scss b/frontend/src/app/themes/guardian/page.scss index e14eacd51e..5b0c555a70 100644 --- a/frontend/src/app/themes/guardian/page.scss +++ b/frontend/src/app/themes/guardian/page.scss @@ -76,7 +76,7 @@ font-weight: 600; color: var(--guardian-header-color, #000); height: 72px; - padding: 26px 0px; + padding: 32px 0px 18px 0px; position: relative; } diff --git a/guardian-service/src/api/helpers/policy-statistics-helpers.ts b/guardian-service/src/api/helpers/policy-statistics-helpers.ts index d75e815339..0709597076 100644 --- a/guardian-service/src/api/helpers/policy-statistics-helpers.ts +++ b/guardian-service/src/api/helpers/policy-statistics-helpers.ts @@ -1,6 +1,7 @@ import { DatabaseServer, PolicyStatistic, SchemaConverterUtils, TopicConfig, TopicHelper, Users, VcDocument, VcHelper } from '@guardian/common'; import { GenerateUUIDv4, IFormulaData, IOwner, IRuleData, IScoreData, IScoreOption, IStatisticConfig, IVariableData, PolicyType, Schema, SchemaCategory, SchemaHelper, SchemaStatus, TopicType } from '@guardian/interfaces'; import { generateSchemaContext } from './schema-publish-helper.js'; +import { createHash } from 'crypto'; export async function addRelationship( messageId: string, @@ -32,26 +33,6 @@ export async function findRelationships( return subDocs.filter((doc) => relationships.has(doc.messageId)); } -export function getVcId(document: VcDocument): string { - let credentialSubject: any = document?.document?.credentialSubject; - if (Array.isArray(credentialSubject)) { - credentialSubject = credentialSubject[0]; - } - if (credentialSubject && credentialSubject.id) { - return credentialSubject.id; - } - return document.id; -} - -export function uniqueDocuments(documents: VcDocument[]): VcDocument[] { - const map = new Map(); - for (const document of documents) { - const id = getVcId(document); - map.set(id, document); - } - return Array.from(map.values()); -} - export async function generateSchema(config: PolicyStatistic, owner: IOwner) { const uuid = GenerateUUIDv4(); const variables = config.config?.variables || []; @@ -349,10 +330,55 @@ function validateRules(data?: IRuleData[]): IRuleData[] { export function validateConfig(data: IStatisticConfig): IStatisticConfig { const config: IStatisticConfig = { - variables: validateVariables(data.variables), - scores: validateScores(data.scores), - formulas: validateFormulas(data.formulas), - rules: validateRules(data.rules), + variables: validateVariables(data?.variables), + scores: validateScores(data?.scores), + formulas: validateFormulas(data?.formulas), + rules: validateRules(data?.rules), } return config; } + +function getSubject(document: VcDocument): any { + let credentialSubject: any = document?.document?.credentialSubject; + if (Array.isArray(credentialSubject)) { + credentialSubject = credentialSubject[0]; + } + if (credentialSubject && credentialSubject.id) { + return credentialSubject; + } + return document; +} + +function getVcHash(document: VcDocument): string { + return document.schema; +} + +export function uniqueDocuments(documents: VcDocument[]): VcDocument[] { + const map = new Map>(); + for (const document of documents) { + const hash = getVcHash(document); + const item = map.get(hash) || (new Map()); + item.set(document.messageId, document); + map.set(hash, item); + } + const result: VcDocument[] = []; + for (const item of map.values()) { + console.log(item.size) + for (const doc of item.values()) { + if (Array.isArray(doc.relationships)) { + for (const messageId of doc.relationships) { + const old = item.get(messageId); + if (old) { + old.__duplicate = true + } + } + } + } + for (const doc of item.values()) { + if (!doc.__duplicate) { + result.push(doc); + } + } + } + return result; +} \ No newline at end of file diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index 26b0644ccb..1713a0342d 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -1,6 +1,6 @@ import { ApiResponse } from './helpers/api-response.js'; -import { DatabaseServer, MessageAction, MessageError, MessageResponse, MessageServer, PinoLogger, PolicyImportExport, PolicyStatistic, StatisticMessage, Users } from '@guardian/common'; -import { EntityStatus, IOwner, MessageAPI, PolicyType, Schema, SchemaStatus } from '@guardian/interfaces'; +import { DatabaseServer, MessageAction, MessageError, MessageResponse, MessageServer, PinoLogger, PolicyImportExport, PolicyStatistic, PolicyStatisticDocument, StatisticAssessmentMessage, StatisticMessage, Users, VcDocument } from '@guardian/common'; +import { DocumentStatus, EntityStatus, IOwner, MessageAPI, PolicyType, Schema, SchemaStatus } from '@guardian/interfaces'; import { publishSchema } from './helpers/index.js'; import { findRelationships, generateSchema, generateVcDocument, getOrCreateTopic, uniqueDocuments, validateConfig } from './helpers/policy-statistics-helpers.js'; @@ -96,6 +96,12 @@ export async function statisticsAPI(logger: PinoLogger): Promise { }, otherOptions ); + for (const item of items) { + (item as any).documents = await DatabaseServer.getStatisticAssessmentCount({ + definitionId: item.id, + owner: owner.owner + }); + } return new MessageResponse({ items, count }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); @@ -313,6 +319,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } else { otherOptions.orderBy = { createDate: 'DESC' }; otherOptions.limit = 100; + otherOptions.offset = 0; } const item = await DatabaseServer.getStatisticById(definitionId); @@ -342,14 +349,11 @@ export async function statisticsAPI(logger: PinoLogger): Promise { schema: { $in: subSchemas } })); - const [targetDocs, count] = await DatabaseServer.getStatisticDocumentsAndCount( - { - policyId, - owner: owner.creator, - schema: { $in: targetSchemas } - }, - otherOptions - ); + const targetDocs = await DatabaseServer.getStatisticDocuments({ + policyId, + owner: owner.creator, + schema: { $in: targetSchemas } + }); const items: any[] = []; for (const target of uniqueDocuments(targetDocs)) { @@ -359,7 +363,10 @@ export async function statisticsAPI(logger: PinoLogger): Promise { unrelatedDocuments: allDocs }) } - return new MessageResponse({ items, count }); + return new MessageResponse({ + items: items.slice(otherOptions.offset, otherOptions.offset + otherOptions.limit), + count: items.length + }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); @@ -402,22 +409,35 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const schema = await DatabaseServer.getSchema({ topicId: item.topicId }); const schemaObject = new Schema(schema); - const vc = await generateVcDocument(assessment.document, schemaObject, owner) + const vcObject = await generateVcDocument(assessment.document, schemaObject, owner); + + const topic = await getOrCreateTopic(item); + const user = await (new Users()).getHederaAccount(owner.creator); + const messageServer = new MessageServer(user.hederaAccountId, user.hederaAccountKey, user.signOptions); - const newItem = { - id: item.id, + const vcMessage = new StatisticAssessmentMessage(MessageAction.CreateStatisticAssessment); + vcMessage.setDefinition(item); + vcMessage.setDocument(vcObject); + vcMessage.setTarget(assessment.target); + vcMessage.setRelationships(assessment.relationships); + const vcMessageResult = await messageServer + .setTopicObject(topic) + .sendMessage(vcMessage); + + const row = await DatabaseServer.createStatisticAssessment({ definitionId: item.id, policyId: item.policyId, + policyTopicId: item.policyTopicId, + policyInstanceTopicId: item.policyInstanceTopicId, creator: owner.creator, owner: owner.owner, - target: assessment.target, - relationships: assessment.relationships, - document: vc.toJsonTree() - } - - console.log('----------------------------') - // const row = await DatabaseServer.createStatisticReport(newItem); - return new MessageResponse(newItem); + messageId: vcMessageResult.getId(), + topicId: vcMessageResult.getTopicId(), + target: vcMessageResult.getTarget(), + relationships: vcMessageResult.getRelationships(), + document: vcMessageResult.getDocument() + }); + return new MessageResponse(row); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); @@ -465,19 +485,20 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } otherOptions.fields = [ 'id', + 'definitionId', + 'policyId', 'creator', 'owner', - 'name', - 'description', - 'status', + 'target', + 'relationships', 'topicId', 'messageId', - 'policyId' + 'document' ]; - console.log('----------------------------') - const [items, count] = await DatabaseServer.getStatisticsAndCount( + const [items, count] = await DatabaseServer.getStatisticAssessmentsAndCount( { + definitionId, owner: owner.owner }, otherOptions @@ -516,8 +537,12 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Item is not published.'); } - console.log('----------------------------') - return new MessageResponse(item); + const result = await DatabaseServer.getStatisticAssessment({ + id: assessmentId, + definitionId, + owner: owner.owner + }); + return new MessageResponse(result); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index abae3bc5dc..e4c7840f69 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -11,7 +11,7 @@ import { Contract, DatabaseServer, DidDocument, DocumentState, DryRun, DryRunFiles, Environment, ExternalDocument, ExternalEventChannel, IPFS, LargePayloadContainer, MessageBrokerChannel, MessageServer, Migration, MintRequest, MintTransaction, mongoForLoggingInitialization, MultiDocuments, MultiPolicy, MultiPolicyTransaction, OldSecretManager, PinoLogger, pinoLoggerInitialization, Policy, PolicyCache, PolicyCacheData, PolicyCategory, - PolicyInvitations, PolicyModule, PolicyProperty, PolicyRoles, PolicyStatistic, PolicyTest, PolicyTool, Record, RetirePool, RetireRequest, Schema, SecretManager, + PolicyInvitations, PolicyModule, PolicyProperty, PolicyRoles, PolicyStatistic, PolicyStatisticDocument, PolicyTest, PolicyTool, Record, RetirePool, RetireRequest, Schema, SecretManager, Settings, SplitDocuments, SuggestionsConfig, Tag, TagCache, Theme, Token, Topic, TopicMemo, TransactionLogger, TransactionLogLvl, Users, ValidateConfiguration, VcDocument, VpDocument, Wallet, WiperRequest, Workers } from '@guardian/common'; @@ -95,7 +95,8 @@ const necessaryEntity = [ PolicyCache, AssignEntity, PolicyTest, - PolicyStatistic + PolicyStatistic, + PolicyStatisticDocument ] Promise.all([ From 333d90fc9094213b3724cb669fe3f99db2084479 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Thu, 3 Oct 2024 18:06:17 +0400 Subject: [PATCH 33/48] update Signed-off-by: Stepan Kiryakov --- .../statistic-preview-dialog.component.html | 88 ++++++++ .../statistic-preview-dialog.component.scss | 157 ++++++++++++++ .../statistic-preview-dialog.component.ts | 113 +++++++++++ .../lang-modes/formula-lang.mode.ts | 7 +- .../policy-statistics/models/schema-node.ts | 19 ++ .../policy-statistics.module.ts | 2 + ...stic-assessment-configuration.component.ts | 3 +- .../statistic-assessment-view.component.html | 109 +++++++++- .../statistic-assessment-view.component.scss | 191 ++++++++++++++++++ .../statistic-assessment-view.component.ts | 135 ++++++++++++- ...ic-definition-configuration.component.html | 80 ++++---- ...ic-definition-configuration.component.scss | 17 +- ...stic-definition-configuration.component.ts | 37 +++- .../statistic-definitions.component.html | 3 +- .../src/api/policy-statistics.service.ts | 70 ++++--- interfaces/src/helpers/schema-helper.ts | 4 +- 16 files changed, 953 insertions(+), 82 deletions(-) create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss create mode 100644 frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html new file mode 100644 index 0000000000..50aac70f5b --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.html @@ -0,0 +1,88 @@ +
+
+
Preview
+
+
+ +
+
+
+
+
+
+
+
+
+ Input Fields +
+
+
+
+ {{variable.description}} +
+
+ +
+
+
+
+ Scores +
+
+
+
+ {{score.description}} +
+
+
+
+ +
+ +
+
+
+
+
+ Formulas +
+
+
+
+ {{formula.description}} +
+
+ {{formula.value}} +
+
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss new file mode 100644 index 0000000000..dd27bddd8c --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.scss @@ -0,0 +1,157 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: column; + 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 { + margin-bottom: 24px; +} + +.dialog-grid-body { + max-height: 256px; + overflow: auto; +} + +.dialog-body { + height: auto; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.option-actions { + padding-top: 24px; + + button { + height: 28px; + width: 120px; + } +} + +.grid-label { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + margin-bottom: 16px; +} + +.preview-container { + display: flex; + flex-direction: column; + overflow: auto; + max-height: calc(100vh - 500px); + padding-right: 16px; +} + +.fields-container { + .fields-header { + color: #181818; + font-size: 16px; + font-weight: 600; + line-height: 20px; + text-align: left; + padding-bottom: 16px; + } + + .field-container { + margin-bottom: 16px; + + .field-name { + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + color: #181818; + padding: 8px 0px; + margin-bottom: 6px; + } + + .field-value { + padding: 12px 16px 12px 16px; + border-radius: 8px; + border: 1px solid #E1E7EF; + background: #F9FAFC; + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: #23252E; + width: 100%; + min-height: 42px; + } + } + + .option-container { + display: flex; + flex-direction: row; + + border-radius: 6px; + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + background: #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; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts new file mode 100644 index 0000000000..949275ed54 --- /dev/null +++ b/frontend/src/app/modules/policy-statistics/dialogs/statistic-preview-dialog/statistic-preview-dialog.component.ts @@ -0,0 +1,113 @@ +import { Component } from '@angular/core'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { IFormula, IVariable } from '../../models/assessment'; +import { GenerateUUIDv4 } from '@guardian/interfaces'; +import { Formula } from 'src/app/utils'; + +@Component({ + selector: 'statistic-preview-dialog', + templateUrl: './statistic-preview-dialog.component.html', + styleUrls: ['./statistic-preview-dialog.component.scss'], +}) +export class StatisticPreviewDialog { + public loading = true; + public item: any; + public preview: any[]; + public scores: any[]; + public formulas: any[]; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.item = this.config.data?.item || {}; + + const configuration = this.item.config || {}; + const variables = configuration.variables || []; + const formulas = configuration.formulas || []; + const scores = configuration.scores || []; + const preview = new Map(); + this.preview = []; + this.scores = []; + this.formulas = []; + + for (const variable of variables) { + this.preview.push({ + id: variable.id, + description: variable.fieldDescription || '', + value: null + }); + } + + for (const score of scores) { + const options: any[] = []; + if (score.options) { + for (const option of score.options) { + options.push({ + id: GenerateUUIDv4(), + description: option.description, + value: option.value + }); + } + } + this.scores.push({ + id: score.id, + description: score.description, + value: null, + options + }); + } + + for (const formula of formulas) { + this.formulas.push({ + id: formula.id, + description: formula.description, + value: null, + formula: formula.formula, + type: formula.type + }); + } + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit() { + const document: any = {}; + + for (const field of this.preview) { + document[field.id] = field.value; + } + + for (const score of this.scores) { + document[score.id] = score.value; + } + + for (const formula of this.formulas) { + formula.value = this.calcFormula(formula, document); + if (formula.type === 'string') { + formula.value = String(formula.value); + } else { + formula.value = Number(formula.value); + } + document[formula.id] = formula.value; + } + } + + private calcFormula(item: IFormula, scope: any): any { + try { + return Formula.evaluate(item.formula, scope); + } catch (error) { + return NaN; + } + } +} diff --git a/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts index 3ede5360a1..3efa4b4f40 100644 --- a/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts +++ b/frontend/src/app/modules/policy-statistics/lang-modes/formula-lang.mode.ts @@ -6,11 +6,12 @@ import 'codemirror/mode/meta'; import CodeMirror, { Mode, StringStream } from 'codemirror'; CodeMirror.defineMode('formula-lang', function (config, parserConfig) { - const variables = (config as any).variables as string[]; - const variablesName = variables.map((v) => `(${v})`).join('|'); - const isVariables = variables.length ? new RegExp(variablesName) : null; const policySyntaxOverlay: Mode = { token: function (stream: StringStream) { + const variables = (config as any).variables as string[]; + const variablesName = variables.map((v) => `(${v})`).join('|'); + const isVariables = variables.length ? new RegExp(variablesName) : null; + if (isVariables && stream.match(isVariables)) { return 'formula-variable'; } else if(stream.match(/([a-zA-Z]+)\(/, false)) { diff --git a/frontend/src/app/modules/policy-statistics/models/schema-node.ts b/frontend/src/app/modules/policy-statistics/models/schema-node.ts index e78466c12f..7f60885afb 100644 --- a/frontend/src/app/modules/policy-statistics/models/schema-node.ts +++ b/frontend/src/app/modules/policy-statistics/models/schema-node.ts @@ -69,4 +69,23 @@ export class SchemaNode extends TreeNode { }) return result; } +} + +export class DocumentNode extends TreeNode { + public override clone(): DocumentNode { + const clone = new DocumentNode(this.id, this.type, this.data); + clone.type = this.type; + clone.data = this.data; + clone.childIds = new Set(this.childIds); + return clone; + } + + public override update() { + } + + public static from(document: any, type: 'root' | 'sub'): DocumentNode { + const id = document.messageId; + const result = new DocumentNode(id, type, document); + return result; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts index 3434abea52..adb7b2496c 100644 --- a/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts +++ b/frontend/src/app/modules/policy-statistics/policy-statistics.module.ts @@ -25,12 +25,14 @@ import { StatisticAssessmentViewComponent } from './statistic-assessment-view/st import { StatisticAssessmentsComponent } from './statistic-assessments/statistic-assessments.component'; import { StatisticDefinitionsComponent } from './statistic-definitions/statistic-definitions.component'; import { StatisticDefinitionConfigurationComponent } from './statistic-definition-configuration/statistic-definition-configuration.component'; +import { StatisticPreviewDialog } from './dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; @NgModule({ declarations: [ NewPolicyStatisticsDialog, ScoreDialog, TreeGraphComponent, + StatisticPreviewDialog, StatisticAssessmentConfigurationComponent, StatisticAssessmentViewComponent, StatisticAssessmentsComponent, diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index 255399d714..b46f5255de 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -452,7 +452,8 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { document[field.id] = field.value; } for (const score of this.scores) { - document[score.id] = score.value; + const option = score.options.find((o) => o.value === score.value); + document[score.id] = option?.description || String(score.value); } for (const formula of this.formulas) { document[formula.id] = formula.value; diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html index f5f110d015..e99386759b 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.html @@ -69,9 +69,13 @@
- \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss index 2d60337228..7cc7eb5304 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss @@ -56,6 +56,7 @@ width: 100%; height: 100%; overflow: hidden; + position: relative; } .nav-body-container { @@ -64,6 +65,7 @@ height: 100%; padding: 24px 48px 24px 48px; overflow: auto; + position: relative; .step-body-container { display: flex; @@ -115,6 +117,8 @@ color: #23252E; width: 100%; min-height: 42px; + overflow: hidden; + text-overflow: ellipsis; } .field-value-array { @@ -186,3 +190,190 @@ } } } + +.tree-container { + position: absolute; + left: 0px; + top: 0px; + bottom: 0px; + right: 0px; + z-index: 1; + user-select: none; + display: flex; + + .tree-node { + background: #ffffff; + border: 1px solid #bac0ce; + box-shadow: 0px 4px 4px 0px #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: #F9FAFC; + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14px; + text-align: left; + 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: #fff; + box-shadow: 0px 0px 1px 3px #eff3f7; + + .zoom-label { + width: 28px; + height: 28px; + border-radius: 8px; + font-family: Inter; + font-size: 8px; + font-weight: 700; + color: #848FA9; + border: 1px solid #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: #fff; + border-left: 1px solid #E1E7EF; + box-shadow: 0px 0px 4px 0px #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: 100%; + cursor: pointer; + top: 17px; + right: 16px; + } + + .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: #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/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts index 02eb872501..de9edf36c7 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts @@ -1,12 +1,17 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { GenerateUUIDv4, IStatistic, UserPermissions } from '@guardian/interfaces'; +import { GenerateUUIDv4, IStatistic, Schema, 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'; import { IFormula, IOption, IScore, IVariable } from '../models/assessment'; +import { TreeSource } from '../tree-graph/tree-source'; +import { TreeGraphComponent } from '../tree-graph/tree-graph.component'; +import { DocumentNode, SchemaData } from '../models/schema-node'; +import { TreeNode } from '../tree-graph/tree-node'; +import { VCViewerDialog } from '../../schema-engine/vc-dialog/vc-dialog.component'; @Component({ selector: 'app-statistic-assessment-view', @@ -17,6 +22,8 @@ export class StatisticAssessmentViewComponent 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; @@ -25,19 +32,35 @@ export class StatisticAssessmentViewComponent implements OnInit { 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 policyEngineService: PolicyEngineService, private dialogService: DialogService, private router: Router, private route: ActivatedRoute @@ -91,7 +114,10 @@ export class StatisticAssessmentViewComponent implements OnInit { this.definition = definition; this.policy = relationships?.policy || {}; this.schemas = relationships?.schemas || []; - this.assessment = assessment || {}; + this.schema = relationships?.schema; + this.assessment = assessment?.document || {}; + this.target = assessment?.target || {}; + this.relationships = assessment?.relationships || []; this.updateMetadata(); setTimeout(() => { @@ -161,7 +187,7 @@ export class StatisticAssessmentViewComponent implements OnInit { options.push({ id: GenerateUUIDv4(), description: option.description, - value: option.value + value: option.description //this is not a typo. }); } } @@ -183,12 +209,113 @@ export class StatisticAssessmentViewComponent implements OnInit { 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, { + width: '65vw', + closable: true, + header: 'VC', + data: { + id: document.id, + dryRun: false, + document: document.document, + title: document.schemaName || 'VC', + type: 'VC', + viewDocument: true, + schema: this.schema + }, + }); + dialogRef.onClose.subscribe(async (result) => { }); } } diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index 998b98483b..c8bf98fb90 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -1,4 +1,4 @@ -
+
@@ -71,11 +71,11 @@
- +
- +
@@ -255,10 +255,12 @@ + [binary]="true" + [disabled]="readonly">
{{item.data.description}} @@ -308,10 +310,12 @@ + [binary]="true" + [disabled]="readonly">
{{item.data.propertyName || item.data.description}} @@ -335,7 +339,8 @@ name="relationships" value="main" [(ngModel)]="rule.type" - inputId="relationships_1"> + inputId="relationships_1" + [disabled]="readonly">
-
+
@@ -431,7 +438,8 @@ [(ngModel)]="variable.id" id="formula.id" pInputText - type="text"/> + type="text" + [readonly]="readonly"/>
@@ -484,7 +492,8 @@ [(ngModel)]="score.id" id="formula.id" pInputText - type="text"/> + type="text" + [readonly]="readonly"/>
+ [options]="codeMirrorOptions" + [disabled]="readonly">
-
+
-
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss index 26cb9e9ec7..af6d798d7f 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss @@ -40,18 +40,17 @@ height: 64px; width: 100%; display: flex; - justify-content: center; + justify-content: flex-end; padding: 12px 48px; background: #fff; border-top: 1px solid #E1E7EF; position: relative; + overflow: hidden; button { - position: absolute; height: 40px; - top: 12px; - right: 48px; width: 135px; + margin-left: 16px; } .guardian-step-container { @@ -442,6 +441,10 @@ padding-left: 8px; min-width: 125px; cursor: pointer; + + &[readonly="true"] { + cursor: default; + } } &[property="false"] { @@ -768,7 +771,7 @@ width: 100%; height: 100%; - .p-multiselect-label { + .p-multiselect-label { padding: 6px 16px 6px 8px; } } @@ -919,4 +922,8 @@ .multiselect-selected-value:last-child { margin-bottom: 0px; +} + +*[codemirror-readonly="true"] { + pointer-events: none; } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts index c33fd55e01..505e71e575 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts @@ -1,6 +1,6 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { IStatistic, Schema, UserPermissions } from '@guardian/interfaces'; +import { EntityStatus, IStatistic, 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'; @@ -18,6 +18,7 @@ import { SchemaScore, SchemaScores } from '../models/schema-scores'; import { DialogService } from 'primeng/dynamicdialog'; import { ScoreDialog } from '../dialogs/score-dialog/score-dialog.component'; import { SchemaRule, SchemaRules } from '../models/schema-rules'; +import { StatisticPreviewDialog } from '../dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; @Component({ selector: 'app-statistic-definition-configuration', @@ -50,6 +51,8 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { public stepper = [true, false, false]; + public readonly: boolean = false; + @ViewChild('fieldTree', { static: false }) fieldTree: ElementRef; @ViewChild('treeTabs', { static: false }) treeTabs: ElementRef; @@ -195,6 +198,7 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { this.schemaService.properties() ]).subscribe(([item, relationships, properties]) => { this.item = item; + this.readonly = this.item?.status === EntityStatus.PUBLISHED; if (relationships) { this.updateTree(relationships, properties); } @@ -299,7 +303,6 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { this._selectTimeout1 = setTimeout(() => { this._updateSelectNode(); }, 350) - } private _updateSelectNode() { @@ -349,6 +352,9 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onSelectField(field: TreeListItem) { + if(this.readonly) { + return; + } field.selected = !field.selected; if (this.rootNode) { const rootView = this.rootNode.fields; @@ -563,4 +569,31 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { this.loading = false; }); } + + public onPreview() { + this.rules.update(this.variables); + const value = this.overviewForm.value; + const config = { + variables: this.variables.getJson(), + formulas: this.formulas.getJson(), + scores: this.scores.getJson(), + rules: this.rules.getJson(), + }; + const item = { + ...this.item, + name: value.name, + description: value.description, + config + }; + const dialogRef = this.dialogService.open(StatisticPreviewDialog, { + showHeader: false, + header: 'Preview', + width: '800px', + styleClass: 'guardian-dialog', + data: { + item + } + }); + dialogRef.onClose.subscribe(async (result) => {}); + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html index e60635f49d..9711ad0d89 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html @@ -107,13 +107,12 @@ >
+ [svgClass]="'icon-color-primary'">
diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index 1713a0342d..03ce538624 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -92,8 +92,11 @@ export async function statisticsAPI(logger: PinoLogger): Promise { ]; const [items, count] = await DatabaseServer.getStatisticsAndCount( { - owner: owner.owner - }, + $or: [ + { status: EntityStatus.PUBLISHED }, + { creator: owner.creator } + ] + } as any, otherOptions ); for (const item of items) { @@ -124,7 +127,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } const { definitionId, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } return new MessageResponse(item); @@ -149,7 +152,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } const { definitionId, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } const policyId = item.policyId; @@ -158,11 +161,20 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Item does not exist.'); } const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); - const all = [].concat(schemas, toolSchemas).filter((s) => s.status === SchemaStatus.PUBLISHED) - return new MessageResponse({ - policy, - schemas: all - }); + const all = [].concat(schemas, toolSchemas).filter((s) => s.status === SchemaStatus.PUBLISHED); + if (item.status === EntityStatus.PUBLISHED) { + const schema = await DatabaseServer.getSchema({ topicId: item.topicId }); + return new MessageResponse({ + policy, + schemas: all, + schema + }); + } else { + return new MessageResponse({ + policy, + schemas: all + }); + } } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); @@ -189,7 +201,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const { definitionId, definition, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!item || item.creator !== owner.creator) { return new MessageError('Item does not exist.'); } if (item.status === EntityStatus.PUBLISHED) { @@ -224,7 +236,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } const { definitionId, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!item || item.creator !== owner.creator) { return new MessageError('Item does not exist.'); } if (item.status === EntityStatus.PUBLISHED) { @@ -254,7 +266,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const { definitionId, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!item || item.creator !== owner.creator) { return new MessageError('Item does not exist.'); } if (item.status === EntityStatus.PUBLISHED) { @@ -323,7 +335,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } @@ -363,9 +375,9 @@ export async function statisticsAPI(logger: PinoLogger): Promise { unrelatedDocuments: allDocs }) } - return new MessageResponse({ - items: items.slice(otherOptions.offset, otherOptions.offset + otherOptions.limit), - count: items.length + return new MessageResponse({ + items: items.slice(otherOptions.offset, otherOptions.offset + otherOptions.limit), + count: items.length }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); @@ -400,7 +412,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Invalid object.'); } const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } if (item.status !== EntityStatus.PUBLISHED) { @@ -465,7 +477,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const { pageIndex, pageSize } = filters; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } if (item.status !== EntityStatus.PUBLISHED) { @@ -498,8 +510,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const [items, count] = await DatabaseServer.getStatisticAssessmentsAndCount( { - definitionId, - owner: owner.owner + definitionId }, otherOptions ); @@ -530,19 +541,32 @@ export async function statisticsAPI(logger: PinoLogger): Promise { const { definitionId, assessmentId, owner } = msg; const item = await DatabaseServer.getStatisticById(definitionId); - if (!item || item.owner !== owner.owner) { + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { return new MessageError('Item does not exist.'); } if (item.status !== EntityStatus.PUBLISHED) { return new MessageError('Item is not published.'); } - const result = await DatabaseServer.getStatisticAssessment({ + const document = await DatabaseServer.getStatisticAssessment({ id: assessmentId, definitionId, owner: owner.owner }); - return new MessageResponse(result); + + const relationships = await DatabaseServer.getStatisticDocuments({ + messageId: { $in: document?.relationships } + }); + + const target = await DatabaseServer.getStatisticDocument({ + messageId: document?.target + }); + + return new MessageResponse({ + document, + target, + relationships + }); } catch (error) { await logger.error(error, ['GUARDIAN_SERVICE']); return new MessageError(error); diff --git a/interfaces/src/helpers/schema-helper.ts b/interfaces/src/helpers/schema-helper.ts index 2f67b1a13e..7b2a4fd88f 100644 --- a/interfaces/src/helpers/schema-helper.ts +++ b/interfaces/src/helpers/schema-helper.ts @@ -43,8 +43,8 @@ export class SchemaHelper { _property = _property.oneOf[0]; } field.name = name; - field.title = _property.title || name; - field.description = _property.description || name; + field.title = property.title || _property.title || name; + field.description = property.description || _property.description || name; field.isArray = _property.type === SchemaDataTypes.array; field.comment = _property.$comment; field.examples = Array.isArray(_property.examples) ? _property.examples : null; From 5d55bf713c11659ef7728d7fbe7bb08dda320ee6 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 4 Oct 2024 15:28:39 +0400 Subject: [PATCH 34/48] fix Signed-off-by: Stepan Kiryakov --- .../statistic-definition-configuration.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index c8bf98fb90..9ced52af47 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -677,7 +677,7 @@
- +
\ No newline at end of file From 87c35c09b55d84bf24806cfe2440c8f7243c0f33 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 4 Oct 2024 16:27:54 +0400 Subject: [PATCH 35/48] fix Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 260 ++++++++++-------- api-gateway/src/helpers/guardians.ts | 46 +++- .../validation/schemas/document.dto.ts | 1 - .../schemas/policy-statistics.dto.ts | 161 ++++++++++- .../score-dialog/score-dialog.component.ts | 1 - ...stic-assessment-configuration.component.ts | 2 +- .../statistic-assessment-view.component.ts | 23 +- .../statistic-assessments.component.ts | 2 +- .../app/services/policy-statistics.service.ts | 4 + .../api/helpers/policy-statistics-helpers.ts | 8 +- .../src/api/policy-statistics.service.ts | 45 ++- .../src/type/messages/message-api.type.ts | 3 +- 12 files changed, 404 insertions(+), 152 deletions(-) diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index c9bac77c75..6485fa9c02 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -1,19 +1,15 @@ import { IAuthUser, PinoLogger } from '@guardian/common'; -import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Req, Response } from '@nestjs/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, StatisticDefinitionDTO, StatisticAssessmentDTO, VcDocumentDTO, pageHeader } from '#middlewares'; -import { UseCache, Guardians, InternalException, EntityOwner, CacheService } from '#helpers'; +import { Examples, InternalServerErrorDTO, StatisticDefinitionDTO, StatisticAssessmentDTO, VcDocumentDTO, pageHeader, StatisticAssessmentRelationshipsDTO, StatisticDefinitionRelationshipsDTO } from '#middlewares'; +import { Guardians, InternalException, EntityOwner } from '#helpers'; import { AuthUser, Auth } from '#auth'; @Controller('policy-statistics') @ApiTags('policy-statistics') export class PolicyStatisticsApi { - constructor( - private readonly cacheService: CacheService, - private readonly logger: PinoLogger - ) { - } + constructor(private readonly logger: PinoLogger) { } /** * Creates a new statistic definition @@ -149,20 +145,25 @@ export class PolicyStatisticsApi { } /** - * Get relationships by id - */ - @Get('/:definitionId/relationships') - @Auth(Permissions.STATISTICS_STATISTIC_READ) + * Update statistic definition + */ + @Put('/:definitionId') + @Auth(Permissions.STATISTICS_STATISTIC_CREATE) @ApiOperation({ - summary: 'Retrieves statistic relationships.', - description: 'Retrieves statistic relationships for the specified ID.' + summary: 'Updates statistic definition.', + description: 'Updates statistic definition configuration for the specified statistic ID.', }) @ApiParam({ name: 'definitionId', - type: String, + type: 'string', + required: true, description: 'Statistic Definition Identifier', + example: Examples.DB_ID, + }) + @ApiBody({ + description: 'Object that contains a configuration.', required: true, - example: Examples.DB_ID + type: StatisticDefinitionDTO }) @ApiOkResponse({ description: 'Successful operation.', @@ -170,94 +171,81 @@ export class PolicyStatisticsApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - type: InternalServerErrorDTO, + type: InternalServerErrorDTO }) @ApiExtraModels(StatisticDefinitionDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - @UseCache() - async getStatisticRelationships( + async updateStatisticDefinition( @AuthUser() user: IAuthUser, - @Param('definitionId') definitionId: string + @Param('definitionId') definitionId: string, + @Body() item: StatisticDefinitionDTO ): Promise { try { if (!definitionId) { throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); } const owner = new EntityOwner(user); - const guardian = new Guardians(); - return await guardian.getStatisticRelationships(definitionId, owner); + const guardians = new Guardians(); + const oldItem = await guardians.getStatisticDefinitionById(definitionId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.updateStatisticDefinition(definitionId, item, owner); } catch (error) { await InternalException(error, this.logger); } } /** - * Get page + * Delete statistic definition */ - @Get('/:definitionId/documents') - @Auth(Permissions.STATISTICS_STATISTIC_READ) + @Delete('/:definitionId') + @Auth(Permissions.STATISTICS_STATISTIC_CREATE) @ApiOperation({ - summary: 'Return a list of all documents.', - description: 'Returns all documents.', + summary: 'Deletes the statistic definition.', + description: 'Deletes the statistic definition with the provided ID.', }) @ApiParam({ name: 'definitionId', - type: String, - description: 'Statistic Definition Identifier', + type: 'string', 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 + description: 'Statistic Definition Identifier', + example: Examples.DB_ID, }) @ApiOkResponse({ description: 'Successful operation.', - isArray: true, - headers: pageHeader, - type: VcDocumentDTO + type: Boolean }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - type: InternalServerErrorDTO, + type: InternalServerErrorDTO }) - @ApiExtraModels(StatisticDefinitionDTO, InternalServerErrorDTO) + @ApiExtraModels(InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - async getStatisticDocuments( + async deleteStatisticDefinition( @AuthUser() user: IAuthUser, - @Response() res: any, - @Param('definitionId') definitionId: string, - @Query('pageIndex') pageIndex?: number, - @Query('pageSize') pageSize?: number - ): Promise { + @Param('definitionId') definitionId: string + ): Promise { try { + if (!definitionId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY) + } const owner = new EntityOwner(user); const guardians = new Guardians(); - const { items, count } = await guardians.getStatisticDocuments(definitionId, owner, pageIndex, pageSize); - return res.header('X-Total-Count', count).send(items); + return await guardians.deleteStatisticDefinition(definitionId, owner); } catch (error) { await InternalException(error, this.logger); } } /** - * Update statistic definition + * Publish statistic definition */ - @Put('/:definitionId') + @Put('/:definitionId/publish') @Auth(Permissions.STATISTICS_STATISTIC_CREATE) @ApiOperation({ - summary: 'Updates statistic definition.', - description: 'Updates statistic definition configuration for the specified statistic ID.', + summary: 'Publishes statistic definition.', + description: 'Publishes statistic definition for the specified statistic ID.', }) @ApiParam({ name: 'definitionId', @@ -266,11 +254,6 @@ export class PolicyStatisticsApi { description: 'Statistic Definition Identifier', example: Examples.DB_ID, }) - @ApiBody({ - description: 'Object that contains a configuration.', - required: true, - type: StatisticDefinitionDTO - }) @ApiOkResponse({ description: 'Successful operation.', type: StatisticDefinitionDTO @@ -281,10 +264,9 @@ export class PolicyStatisticsApi { }) @ApiExtraModels(StatisticDefinitionDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - async updateStatisticDefinition( + async publishStatisticDefinition( @AuthUser() user: IAuthUser, - @Param('definitionId') definitionId: string, - @Body() item: StatisticDefinitionDTO + @Param('definitionId') definitionId: string ): Promise { try { if (!definitionId) { @@ -296,49 +278,108 @@ export class PolicyStatisticsApi { if (!oldItem) { throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); } - return await guardians.updateStatisticDefinition(definitionId, item, owner); + return await guardians.publishStatisticDefinition(definitionId, owner); } catch (error) { await InternalException(error, this.logger); } } /** - * Delete statistic definition + * Get relationships by id */ - @Delete('/:definitionId') - @Auth(Permissions.STATISTICS_STATISTIC_CREATE) + @Get('/:definitionId/relationships') + @Auth(Permissions.STATISTICS_STATISTIC_READ) @ApiOperation({ - summary: 'Deletes the statistic definition.', - description: 'Deletes the statistic definition with the provided ID.', + summary: 'Retrieves statistic relationships.', + description: 'Retrieves statistic relationships for the specified ID.' }) @ApiParam({ name: 'definitionId', - type: 'string', - required: true, + type: String, description: 'Statistic Definition Identifier', - example: Examples.DB_ID, + required: true, + example: Examples.DB_ID }) @ApiOkResponse({ description: 'Successful operation.', - type: Boolean + type: StatisticDefinitionRelationshipsDTO }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - type: InternalServerErrorDTO + type: InternalServerErrorDTO, }) - @ApiExtraModels(InternalServerErrorDTO) + @ApiExtraModels(StatisticDefinitionRelationshipsDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - async deleteStatisticDefinition( + async getStatisticRelationships( @AuthUser() user: IAuthUser, @Param('definitionId') definitionId: string - ): Promise { + ): Promise { try { if (!definitionId) { - throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY) + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getStatisticRelationships(definitionId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/:definitionId/documents') + @Auth(Permissions.STATISTICS_STATISTIC_READ) + @ApiOperation({ + summary: 'Return a list of all documents.', + description: 'Returns all documents.', + }) + @ApiParam({ + name: 'definitionId', + type: String, + description: 'Statistic Definition 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 getStatisticDocuments( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Param('definitionId') definitionId: string, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number + ): Promise { + try { const owner = new EntityOwner(user); const guardians = new Guardians(); - return await guardians.deleteStatisticDefinition(definitionId, owner); + const { items, count } = await guardians.getStatisticDocuments(definitionId, owner, pageIndex, pageSize); + return res.header('X-Total-Count', count).send(items); } catch (error) { await InternalException(error, this.logger); } @@ -463,8 +504,8 @@ export class PolicyStatisticsApi { @Get('/:definitionId/assessment/:assessmentId') @Auth(Permissions.STATISTICS_STATISTIC_READ) @ApiOperation({ - summary: 'Retrieves statistic relationships.', - description: 'Retrieves statistic relationships for the specified ID.' + summary: 'Retrieves statistic assessment.', + description: 'Retrieves statistic assessment for the specified ID.' }) @ApiParam({ name: 'definitionId', @@ -490,7 +531,6 @@ export class PolicyStatisticsApi { }) @ApiExtraModels(StatisticDefinitionDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - @UseCache() async getStatisticAssessment( @AuthUser() user: IAuthUser, @Param('definitionId') definitionId: string, @@ -509,46 +549,50 @@ export class PolicyStatisticsApi { } /** - * Publish statistic definition + * Get assessment relationships */ - @Put('/:definitionId/publish') - @Auth(Permissions.STATISTICS_STATISTIC_CREATE) + @Get('/:definitionId/assessment/:assessmentId/relationships') + @Auth(Permissions.STATISTICS_STATISTIC_READ) @ApiOperation({ - summary: 'Publishes statistic definition.', - description: 'Publishes statistic definition for the specified statistic ID.', + summary: 'Retrieves assessment relationships.', + description: 'Retrieves assessment relationships for the specified ID.' }) @ApiParam({ name: 'definitionId', - type: 'string', - required: true, + type: String, description: 'Statistic Definition Identifier', - example: Examples.DB_ID, + required: true, + example: Examples.DB_ID + }) + @ApiParam({ + name: 'assessmentId', + type: String, + description: 'Statistic Assessment Identifier', + required: true, + example: Examples.DB_ID }) @ApiOkResponse({ description: 'Successful operation.', - type: StatisticDefinitionDTO + type: StatisticAssessmentRelationshipsDTO }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - type: InternalServerErrorDTO + type: InternalServerErrorDTO, }) @ApiExtraModels(StatisticDefinitionDTO, InternalServerErrorDTO) @HttpCode(HttpStatus.OK) - async publishStatisticDefinition( + async getStatisticAssessmentRelationships( @AuthUser() user: IAuthUser, - @Param('definitionId') definitionId: string - ): Promise { + @Param('definitionId') definitionId: string, + @Param('assessmentId') assessmentId: string + ): Promise { try { - if (!definitionId) { + if (!definitionId || !assessmentId) { throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); } const owner = new EntityOwner(user); - const guardians = new Guardians(); - const oldItem = await guardians.getStatisticDefinitionById(definitionId, owner); - if (!oldItem) { - throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); - } - return await guardians.publishStatisticDefinition(definitionId, owner); + const guardian = new Guardians(); + return await guardian.getStatisticAssessmentRelationships(definitionId, assessmentId, 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 5de37b9b44..6058166f4d 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -36,7 +36,9 @@ import { TokenDTO, ToolDTO, StatisticDefinitionDTO, - StatisticAssessmentDTO + StatisticAssessmentDTO, + StatisticAssessmentRelationshipsDTO, + StatisticDefinitionRelationshipsDTO } from '#middlewares'; /** @@ -2849,7 +2851,7 @@ export class Guardians extends NatsService { /** * Create statistic definition - * + * * @param definition * @param owner * @returns statistic @@ -2872,7 +2874,7 @@ export class Guardians extends NatsService { /** * Get statistic definition - * + * * @param id * @param owner * @returns Operation Success @@ -2883,13 +2885,13 @@ export class Guardians extends NatsService { /** * Get relationships - * + * * @param id * @param owner - * + * * @returns Relationships */ - public async getStatisticRelationships(definitionId: string, owner: IOwner): Promise { + public async getStatisticRelationships(definitionId: string, owner: IOwner): Promise { return await this.sendMessage(MessageAPI.GET_STATISTIC_RELATIONSHIPS, { definitionId, owner }); } @@ -2914,10 +2916,11 @@ export class Guardians extends NatsService { /** * Update statistic definition - * + * * @param id * @param definition * @param owner + * * @returns theme */ public async updateStatisticDefinition( @@ -2930,9 +2933,10 @@ export class Guardians extends NatsService { /** * Delete statistic definition - * + * * @param id * @param owner + * * @returns Operation Success */ public async deleteStatisticDefinition(definitionId: string, owner: IOwner): Promise { @@ -2941,9 +2945,10 @@ export class Guardians extends NatsService { /** * Delete statistic definition - * + * * @param id * @param owner + * * @returns Operation Success */ public async publishStatisticDefinition(definitionId: string, owner: IOwner): Promise { @@ -2952,10 +2957,11 @@ export class Guardians extends NatsService { /** * Create statistic assessment - * + * * @param id * @param report * @param owner + * * @returns statistic report */ public async createStatisticAssessment( @@ -2985,10 +2991,11 @@ export class Guardians extends NatsService { /** * Get statistic assessment - * + * * @param definitionId * @param assessmentId * @param owner + * * @returns Operation Success */ public async getStatisticAssessment( @@ -2998,4 +3005,21 @@ export class Guardians extends NatsService { ): Promise { return await this.sendMessage(MessageAPI.GET_STATISTIC_ASSESSMENT, { definitionId, assessmentId, owner }); } + + /** + * Get statistic assessment relationships + * + * @param definitionId + * @param assessmentId + * @param owner + * + * @returns Operation Success + */ + public async getStatisticAssessmentRelationships( + definitionId: string, + assessmentId: string, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.GET_STATISTIC_ASSESSMENT_RELATIONSHIPS, { definitionId, assessmentId, owner }); + } } diff --git a/api-gateway/src/middlewares/validation/schemas/document.dto.ts b/api-gateway/src/middlewares/validation/schemas/document.dto.ts index 5b12da1887..e1ea9d59c0 100644 --- a/api-gateway/src/middlewares/validation/schemas/document.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/document.dto.ts @@ -163,7 +163,6 @@ export class VpDocumentDTO { document?: VpDTO[]; } - @ApiExtraModels(VcDTO) export class VcDocumentDTO { @ApiProperty({ diff --git a/api-gateway/src/middlewares/validation/schemas/policy-statistics.dto.ts b/api-gateway/src/middlewares/validation/schemas/policy-statistics.dto.ts index ff76260598..c3ee8634a2 100644 --- a/api-gateway/src/middlewares/validation/schemas/policy-statistics.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/policy-statistics.dto.ts @@ -1,7 +1,10 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; import { Examples } from '../examples.js'; -import { IsObject, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsObject, IsOptional, IsString } from 'class-validator'; import { EntityStatus } from '@guardian/interfaces'; +import { VpDocumentDTO } from './document.dto.js'; +import { PolicyDTO } from './policies.dto.js'; +import { SchemaDTO } from './schemas.dto.js'; export class StatisticDefinitionDTO { @ApiProperty({ @@ -110,14 +113,116 @@ export class StatisticDefinitionDTO { @IsString() status?: EntityStatus; + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + config?: any; +} + +export class StatisticAssessmentDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + definitionId?: 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: '' + example: Examples.ACCOUNT_ID }) @IsOptional() @IsString() - method?: string; + policyInstanceTopicId: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + topicId: 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.MESSAGE_ID + }) + @IsOptional() + @IsString() + target: string; + + @ApiProperty({ + type: 'string', + required: false, + isArray: true, + example: [Examples.MESSAGE_ID] + }) + @IsOptional() + @IsArray() + relationships?: string[]; @ApiProperty({ type: 'object', @@ -126,9 +231,53 @@ export class StatisticDefinitionDTO { }) @IsOptional() @IsObject() - config?: any; + document?: any; } -export class StatisticAssessmentDTO { +@ApiExtraModels(VpDocumentDTO) +export class StatisticAssessmentRelationshipsDTO { + @ApiProperty({ + type: () => VpDocumentDTO, + required: false, + }) + @IsOptional() + @IsObject() + target?: VpDocumentDTO; + + @ApiProperty({ + type: () => VpDocumentDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + relationships?: VpDocumentDTO[]; +} + +@ApiExtraModels(PolicyDTO, SchemaDTO) +export class StatisticDefinitionRelationshipsDTO { + @ApiProperty({ + type: () => PolicyDTO, + required: false, + }) + @IsOptional() + @IsObject() + policy?: PolicyDTO; + @ApiProperty({ + type: () => SchemaDTO, + required: false, + isArray: true, + }) + @IsOptional() + @IsArray() + schemas?: SchemaDTO[]; + + @ApiProperty({ + type: () => SchemaDTO, + required: false, + }) + @IsOptional() + @IsObject() + schema?: SchemaDTO; } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts index a969f7c67b..42c3a8e664 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts +++ b/frontend/src/app/modules/policy-statistics/dialogs/score-dialog/score-dialog.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; @Component({ diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index 3752a81270..2e4aca2d8a 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +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'; diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts index de9edf36c7..1546c50016 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.ts @@ -1,8 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { GenerateUUIDv4, IStatistic, Schema, UserPermissions } from '@guardian/interfaces'; +import { GenerateUUIDv4, Schema, 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'; @@ -110,14 +109,20 @@ export class StatisticAssessmentViewComponent implements OnInit { this.policyStatisticsService.getDefinition(this.definitionId), this.policyStatisticsService.getRelationships(this.definitionId), this.policyStatisticsService.getAssessment(this.definitionId, this.assessmentId), - ]).subscribe(([definition, relationships, assessment]) => { + this.policyStatisticsService.getAssessmentRelationships(this.definitionId, this.assessmentId), + ]).subscribe(([ + definition, + definitionRelationships, + assessment, + assessmentRelationships + ]) => { this.definition = definition; - this.policy = relationships?.policy || {}; - this.schemas = relationships?.schemas || []; - this.schema = relationships?.schema; - this.assessment = assessment?.document || {}; - this.target = assessment?.target || {}; - this.relationships = assessment?.relationships || []; + 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(() => { diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts index d9b55f3331..758bb6e042 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { EntityStatus, UserPermissions } from '@guardian/interfaces'; +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'; diff --git a/frontend/src/app/services/policy-statistics.service.ts b/frontend/src/app/services/policy-statistics.service.ts index 090f0c811a..b5b88fad2e 100644 --- a/frontend/src/app/services/policy-statistics.service.ts +++ b/frontend/src/app/services/policy-statistics.service.ts @@ -87,6 +87,10 @@ export class PolicyStatisticsService { return this.http.get(`${this.url}/${definitionId}/assessment/${assessmentId}`); } + public getAssessmentRelationships(definitionId: string, assessmentId: any): Observable { + return this.http.get(`${this.url}/${definitionId}/assessment/${assessmentId}/relationships`); + } + public getRelationships(definitionId: string): Observable { return this.http.get(`${this.url}/${definitionId}/relationships`); } diff --git a/guardian-service/src/api/helpers/policy-statistics-helpers.ts b/guardian-service/src/api/helpers/policy-statistics-helpers.ts index 0709597076..be773d2847 100644 --- a/guardian-service/src/api/helpers/policy-statistics-helpers.ts +++ b/guardian-service/src/api/helpers/policy-statistics-helpers.ts @@ -1,7 +1,6 @@ import { DatabaseServer, PolicyStatistic, SchemaConverterUtils, TopicConfig, TopicHelper, Users, VcDocument, VcHelper } from '@guardian/common'; import { GenerateUUIDv4, IFormulaData, IOwner, IRuleData, IScoreData, IScoreOption, IStatisticConfig, IVariableData, PolicyType, Schema, SchemaCategory, SchemaHelper, SchemaStatus, TopicType } from '@guardian/interfaces'; import { generateSchemaContext } from './schema-publish-helper.js'; -import { createHash } from 'crypto'; export async function addRelationship( messageId: string, @@ -162,8 +161,9 @@ export async function generateVcDocument(document: any, schema: Schema, owner: I } export async function getOrCreateTopic(item: PolicyStatistic): Promise { + let topic: TopicConfig; if (item.topicId) { - const topic = await TopicConfig.fromObject(await DatabaseServer.getTopicById(item.topicId), true); + topic = await TopicConfig.fromObject(await DatabaseServer.getTopicById(item.topicId), true); if (topic) { return topic; } @@ -177,7 +177,7 @@ export async function getOrCreateTopic(item: PolicyStatistic): Promise { return new MessageError('Invalid parameters.'); } const { definitionId, assessmentId, owner } = msg; - - const item = await DatabaseServer.getStatisticById(definitionId); - if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + const document = await DatabaseServer.getStatisticAssessment({ + id: assessmentId, + definitionId, + owner: owner.owner + }); + if (!document) { return new MessageError('Item does not exist.'); } - if (item.status !== EntityStatus.PUBLISHED) { - return new MessageError('Item is not published.'); - } + return new MessageResponse(document); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get statistic assessment relationships + * + * @param {any} msg - statistic id + * + * @returns {any} - Operation success + */ + ApiResponse(MessageAPI.GET_STATISTIC_ASSESSMENT_RELATIONSHIPS, + async (msg: { + definitionId: string, + assessmentId: string, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { definitionId, assessmentId, owner } = msg; const document = await DatabaseServer.getStatisticAssessment({ id: assessmentId, definitionId, owner: owner.owner }); + if (!document) { + return new MessageError('Item does not exist.'); + } const relationships = await DatabaseServer.getStatisticDocuments({ messageId: { $in: document?.relationships } @@ -563,7 +591,6 @@ export async function statisticsAPI(logger: PinoLogger): Promise { }); return new MessageResponse({ - document, target, relationships }); diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index bfc1153c39..0314c58c6a 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -227,7 +227,8 @@ export enum MessageAPI { PUBLISH_STATISTIC_DEFINITION = 'PUBLISH_STATISTIC_DEFINITION', GET_STATISTIC_ASSESSMENTS = 'GET_STATISTIC_ASSESSMENTS', GET_STATISTIC_ASSESSMENT = 'GET_STATISTIC_ASSESSMENT', - CREATE_STATISTIC_ASSESSMENT = 'CREATE_STATISTIC_ASSESSMENT' + CREATE_STATISTIC_ASSESSMENT = 'CREATE_STATISTIC_ASSESSMENT', + GET_STATISTIC_ASSESSMENT_RELATIONSHIPS = 'GET_STATISTIC_ASSESSMENT_RELATIONSHIPS', } /** From 4b929b5e452de7074ff406d940e4b0eff868fd87 Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Fri, 4 Oct 2024 12:30:55 +0000 Subject: [PATCH 36/48] [skip ci] Add swagger.yaml --- swagger.yaml | 124 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 13 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 4154024b9e..31a962abd7 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -12166,6 +12166,39 @@ paths: tags: *ref_27 security: - bearer: [] + /policy-statistics/{definitionId}/publish: + put: + operationId: PolicyStatisticsApi_publishStatisticDefinition + summary: Publishes statistic definition. + description: Publishes statistic definition for the specified statistic ID. + parameters: + - name: definitionId + required: true + in: path + description: Statistic Definition Identifier + example: '000000000000000000000001' + schema: + type: string + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/StatisticDefinitionDTO' + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_27 + security: + - bearer: [] /policy-statistics/{definitionId}/relationships: get: operationId: PolicyStatisticsApi_getStatisticRelationships @@ -12185,7 +12218,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StatisticDefinitionDTO' + $ref: '#/components/schemas/StatisticDefinitionRelationshipsDTO' '401': description: Unauthorized. '403': @@ -12353,8 +12386,8 @@ paths: /policy-statistics/{definitionId}/assessment/{assessmentId}: get: operationId: PolicyStatisticsApi_getStatisticAssessment - summary: Retrieves statistic relationships. - description: Retrieves statistic relationships for the specified ID. + summary: Retrieves statistic assessment. + description: Retrieves statistic assessment for the specified ID. parameters: - name: definitionId required: true @@ -12390,11 +12423,11 @@ paths: tags: *ref_27 security: - bearer: [] - /policy-statistics/{definitionId}/publish: - put: - operationId: PolicyStatisticsApi_publishStatisticDefinition - summary: Publishes statistic definition. - description: Publishes statistic definition for the specified statistic ID. + /policy-statistics/{definitionId}/assessment/{assessmentId}/relationships: + get: + operationId: PolicyStatisticsApi_getStatisticAssessmentRelationships + summary: Retrieves assessment relationships. + description: Retrieves assessment relationships for the specified ID. parameters: - name: definitionId required: true @@ -12403,13 +12436,20 @@ paths: example: '000000000000000000000001' schema: type: string + - name: assessmentId + required: true + in: path + description: Statistic Assessment Identifier + example: '000000000000000000000001' + schema: + type: string responses: '200': description: Successful operation. content: application/json: schema: - $ref: '#/components/schemas/StatisticDefinitionDTO' + $ref: '#/components/schemas/StatisticAssessmentRelationshipsDTO' '401': description: Unauthorized. '403': @@ -15757,17 +15797,75 @@ components: - PUBLISHED - ERROR example: DRAFT - method: - type: string - example: '' config: type: object nullable: true required: - name + StatisticDefinitionRelationshipsDTO: + type: object + properties: + policy: + $ref: '#/components/schemas/PolicyDTO' + schemas: + type: array + items: + $ref: '#/components/schemas/SchemaDTO' + schema: + $ref: '#/components/schemas/SchemaDTO' StatisticAssessmentDTO: type: object - properties: {} + properties: + id: + type: string + example: '000000000000000000000001' + definitionId: + type: string + example: '000000000000000000000001' + policyId: + type: string + example: '000000000000000000000001' + policyTopicId: + type: string + example: 0.0.1 + policyInstanceTopicId: + type: string + example: 0.0.1 + topicId: + type: string + example: 0.0.1 + 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' + target: + type: string + example: '0000000000.000000001' + relationships: + example: + - '0000000000.000000001' + type: array + items: + type: string + document: + type: object + nullable: true + StatisticAssessmentRelationshipsDTO: + type: object + properties: + target: + $ref: '#/components/schemas/VpDocumentDTO' + relationships: + type: array + items: + $ref: '#/components/schemas/VpDocumentDTO' WorkersTasksDTO: type: object properties: From bf2c05680cafc3637cd6f9b9878c44e8672e956e Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Fri, 4 Oct 2024 17:41:46 +0400 Subject: [PATCH 37/48] fix Signed-off-by: Stepan Kiryakov --- .../src/api/service/policy-statistics.ts | 12 +- ...ic-assessment-configuration.component.scss | 41 ++++--- .../statistic-assessment-view.component.scss | 63 +++++----- .../statistic-assessments.component.scss | 4 +- .../statistic-assessments.component.ts | 8 +- ...ic-definition-configuration.component.html | 8 +- ...ic-definition-configuration.component.scss | 108 ++++++++++-------- .../statistic-definitions.component.scss | 2 +- .../statistic-definitions.component.ts | 8 +- frontend/src/app/themes/guardian/dialog.scss | 10 +- frontend/src/app/themes/guardian/grid.scss | 46 +++++++- .../src/app/themes/guardian/variables.scss | 5 + .../src/api/policy-statistics.service.ts | 21 ++-- 13 files changed, 201 insertions(+), 135 deletions(-) diff --git a/api-gateway/src/api/service/policy-statistics.ts b/api-gateway/src/api/service/policy-statistics.ts index 6485fa9c02..3befb0db0d 100644 --- a/api-gateway/src/api/service/policy-statistics.ts +++ b/api-gateway/src/api/service/policy-statistics.ts @@ -74,6 +74,13 @@ export class PolicyStatisticsApi { required: false, example: 20 }) + @ApiQuery({ + name: 'policyInstanceTopicId', + type: String, + description: 'Policy Instance Topic Id', + required: false, + example: Examples.ACCOUNT_ID + }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, @@ -90,12 +97,13 @@ export class PolicyStatisticsApi { @AuthUser() user: IAuthUser, @Response() res: any, @Query('pageIndex') pageIndex?: number, - @Query('pageSize') pageSize?: number + @Query('pageSize') pageSize?: number, + @Query('policyInstanceTopicId') policyInstanceTopicId?: string ): Promise { try { const owner = new EntityOwner(user); const guardians = new Guardians(); - const { items, count } = await guardians.getStatisticDefinitions({ pageIndex, pageSize }, owner); + const { items, count } = await guardians.getStatisticDefinitions({ policyInstanceTopicId, pageIndex, pageSize }, owner); return res.header('X-Total-Count', count).send(items); } catch (error) { await InternalException(error, this.logger); diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss index 07b91d451a..c3f4921b3a 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss @@ -12,17 +12,16 @@ .guardian-user-back-button { button { - border-color: #fff; + border-color: var(--guardian-background, #FFFFFF); } } .guardian-user-page-header { - color: #fff; + color: var(--guardian-background, #FFFFFF); } - .policy-name { - color: #fff; + color: var(--guardian-background, #FFFFFF); font-size: 14px; font-weight: 500; line-height: 16px; @@ -43,8 +42,8 @@ display: flex; justify-content: flex-end; padding: 12px 48px; - background: #fff; - border-top: 1px solid #E1E7EF; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); position: relative; button { @@ -72,8 +71,8 @@ height: 337px; padding: 8px 0px 8px 0px; border-radius: 8px; - box-shadow: 0px 4px 8px 0px #00000014; - background: #fff; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); .guardian-step { padding-left: 24px; @@ -98,8 +97,8 @@ min-height: 100px; height: fit-content; border-radius: 8px; - box-shadow: 0px 4px 8px 0px #00000014; - background: #fff; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); padding: 24px; flex-direction: column; margin-bottom: 16px; @@ -109,7 +108,7 @@ font-weight: 600; line-height: 32px; text-align: left; - color: #181818; + color: var(--guardian-font-color, #23252E); margin-bottom: 24px; } } @@ -119,10 +118,10 @@ cursor: pointer; &:hover { - background: #f0f3fc !important; + background: var(--guardian-hover, #F0F3FC) !important; td { - background: #f0f3fc !important; + background: var(--guardian-hover, #F0F3FC) !important; } } } @@ -137,21 +136,21 @@ font-weight: 500; line-height: 14px; text-align: left; - color: #181818; + color: var(--guardian-font-color, #23252E); padding: 8px 0px; } .field-value { padding: 12px 16px 12px 16px; border-radius: 8px; - border: 1px solid #E1E7EF; - background: #F9FAFC; + 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: #23252E; + color: var(--guardian-font-color, #23252E); width: 100%; min-height: 42px; } @@ -170,7 +169,7 @@ .scores-container { .score-container { - border: 1px solid #AAB7C4; + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); padding: 24px; border-radius: 8px; margin-bottom: 24px; @@ -181,7 +180,7 @@ font-weight: 700; line-height: 18px; text-align: left; - color: #000000; + color: var(--guardian-font-color, #23252E); margin-bottom: 16px; padding: 8px 0px; } @@ -200,7 +199,7 @@ } &:not([disabled]):hover { - background: #f0f3fc; + background: var(--guardian-hover, #F0F3FC); } .option-checkbox { @@ -278,7 +277,7 @@ } &:hover { - background: #f0f3fc; + background: var(--guardian-hover, #F0F3FC); } } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss index 7cc7eb5304..b18815747b 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss @@ -14,17 +14,17 @@ width: 200px; button { - border-color: #fff; + border-color: var(--guardian-background, #FFFFFF); } } .guardian-user-page-header { - color: #fff; + color: var(--guardian-background, #FFFFFF); } .policy-name { - color: #fff; + color: var(--guardian-background, #FFFFFF); font-size: 14px; font-weight: 500; line-height: 16px; @@ -45,10 +45,10 @@ display: flex; justify-content: center; padding: 0px; - background: #fff; - border-bottom: 1px solid #E1E7EF; + background: var(--guardian-background, #FFFFFF); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); position: relative; - box-shadow: 0px 4px 8px 0px #00000014; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); } .body-container { @@ -73,8 +73,9 @@ min-height: 100px; height: fit-content; border-radius: 8px; - box-shadow: 0px 4px 8px 0px #00000014; - background: #fff; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); + ; padding: 24px; flex-direction: column; margin-bottom: 16px; @@ -84,7 +85,7 @@ font-weight: 600; line-height: 32px; text-align: left; - color: #181818; + color: var(--guardian-font-color, #23252E); margin-bottom: 24px; } } @@ -100,21 +101,21 @@ font-weight: 500; line-height: 14px; text-align: left; - color: #181818; + color: var(--guardian-font-color, #23252E); padding: 8px 0px; } .field-value { padding: 12px 16px 12px 16px; border-radius: 8px; - border: 1px solid #E1E7EF; - background: #F9FAFC; + 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: #23252E; + color: var(--guardian-font-color, #23252E); width: 100%; min-height: 42px; overflow: hidden; @@ -135,7 +136,7 @@ .scores-container { .score-container { - border: 1px solid #AAB7C4; + border: 1px solid var(--guardian-grey-color-3, #AAB7C4); padding: 24px; border-radius: 8px; margin-bottom: 24px; @@ -146,7 +147,7 @@ font-weight: 700; line-height: 18px; text-align: left; - color: #000000; + color: var(--guardian-font-color, #23252E); margin-bottom: 16px; padding: 8px 0px; } @@ -165,7 +166,7 @@ } &:not([disabled]):hover { - background: #f0f3fc; + background: var(--guardian-hover, #F0F3FC); } .option-checkbox { @@ -202,9 +203,9 @@ display: flex; .tree-node { - background: #ffffff; + background: var(--guardian-background, #FFFFFF); border: 1px solid #bac0ce; - box-shadow: 0px 4px 4px 0px #00000014; + box-shadow: 0px 4px 4px 0px var(--guardian-shadow, #00000014); border-radius: 6px; width: 150px; cursor: pointer; @@ -251,13 +252,13 @@ .node-header { width: 100%; padding: 9px 8px 9px 16px; - background: #F9FAFC; + background: var(--guardian-grey-background, #F9FAFC); font-family: Inter; font-size: 12px; font-weight: 600; line-height: 14px; text-align: left; - color: #23252E; + color: var(--guardian-font-color, #23252E); } } } @@ -286,8 +287,8 @@ height: 28px; margin-bottom: 8px; border-radius: 8px; - background: #fff; - box-shadow: 0px 0px 1px 3px #eff3f7; + background: var(--guardian-background, #FFFFFF); + box-shadow: 0px 0px 1px 3px var(--guardian-grey-color, #EFF3F7); .zoom-label { width: 28px; @@ -296,8 +297,8 @@ font-family: Inter; font-size: 8px; font-weight: 700; - color: #848FA9; - border: 1px solid #E1E7EF; + color: var(--guardian-disabled-color, #848FA9); + border: 1px solid var(--guardian-border-color, #E1E7EF); display: flex; justify-content: center; align-items: center; @@ -319,9 +320,9 @@ right: 0px; width: 400px; z-index: 3; - background: #fff; - border-left: 1px solid #E1E7EF; - box-shadow: 0px 0px 4px 0px #00000014; + 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; @@ -341,10 +342,14 @@ max-width: 32px; position: absolute; padding: 4px; - border-radius: 100%; + border-radius: 6px; cursor: pointer; top: 17px; right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } } .schema-fields-container { @@ -360,7 +365,7 @@ font-weight: 600; line-height: 18px; text-align: left; - color: #23252E; + color: var(--guardian-font-color, #23252E); position: relative; } diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss index c46f34e8f2..e99f4ea49c 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.scss @@ -1,5 +1,5 @@ .policy-name { - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); font-size: 14px; font-weight: 500; line-height: 16px; @@ -18,6 +18,6 @@ margin-right: 16px; &:last-child { - margin-right: 0px; + margin-right: 0px; } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts index 758bb6e042..197677cbf2 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessments/statistic-assessments.component.ts @@ -65,25 +65,25 @@ export class StatisticAssessmentsComponent implements OnInit { id: 'topicId', title: 'Topic', type: 'text', - size: 'auto', + size: '150', tooltip: false }, { id: 'target', title: 'Target', type: 'text', - size: 'auto', + size: '200', tooltip: false }, { id: 'messageId', title: 'Message ID', type: 'text', - size: 'auto', + size: '200', tooltip: false }, { id: 'options', title: '', type: 'text', - size: '100', + size: '135', tooltip: false }] } diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index 75d9fb203d..37d74b6cb7 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -575,7 +575,7 @@
There were no scores created yet.
-
+
-
+
-
+
-
+
diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss index af6d798d7f..625ca60f23 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss @@ -11,17 +11,17 @@ .guardian-user-back-button { button { - border-color: #fff; + border-color: var(--guardian-background, #FFFFFF); } } .guardian-user-page-header { - color: #fff; + color: var(--guardian-background, #FFFFFF); } .policy-name { - color: #fff; + color: var(--guardian-background, #FFFFFF); font-size: 14px; font-weight: 500; line-height: 16px; @@ -42,8 +42,8 @@ display: flex; justify-content: flex-end; padding: 12px 48px; - background: #fff; - border-top: 1px solid #E1E7EF; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); position: relative; overflow: hidden; @@ -66,10 +66,10 @@ display: flex; justify-content: center; padding: 0px; - background: #fff; - border-bottom: 1px solid #E1E7EF; + background: var(--guardian-background, #FFFFFF); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); position: relative; - box-shadow: 0px 4px 8px 0px #00000014; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); } .body-container { @@ -82,8 +82,8 @@ padding: 24px 48px; form { - box-shadow: 0px 4px 8px 0px #00000014; - background: #fff; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); padding: 24px 24px 2px 24px; border-radius: 8px; @@ -138,13 +138,13 @@ right: 0px; height: 40px; z-index: 2; - background: #F9FAFC; + background: var(--guardian-grey-background, #F9FAFC); padding: 8px 16px 0px 16px; display: flex; flex-direction: row; - border-top: 1px solid #E1E7EF; + border-top: 1px solid var(--guardian-border-color, #E1E7EF); user-select: none; - box-shadow: 0px 0px 4px 0px #00000014; + box-shadow: 0px 0px 4px 0px var(--guardian-shadow, #00000014); .tree-tabs { display: flex; @@ -211,13 +211,13 @@ .tree-tab { padding: 8px 16px 8px 16px; - background: #FFFFFF; + background: var(--guardian-background, #FFFFFF); font-family: Inter; font-size: 12px; font-weight: 500; line-height: 14px; text-align: left; - color: #23252E; + color: var(--guardian-font-color, #23252E); border: 1px solid #C4D0E1; border-radius: 6px; border-bottom-width: 0; @@ -254,7 +254,7 @@ padding: 0px 8px; border-radius: 16px; cursor: pointer; - color: #fff; + color: var(--guardian-background, #FFFFFF); font-size: 10px; height: 16px; position: relative; @@ -293,8 +293,8 @@ height: 28px; margin-bottom: 8px; border-radius: 8px; - background: #fff; - box-shadow: 0px 0px 1px 3px #eff3f7; + background: var(--guardian-background, #FFFFFF); + box-shadow: 0px 0px 1px 3px var(--guardian-grey-color, #EFF3F7); .zoom-label { width: 28px; @@ -303,8 +303,8 @@ font-family: Inter; font-size: 8px; font-weight: 700; - color: #848FA9; - border: 1px solid #E1E7EF; + color: var(--guardian-disabled-color, #848FA9); + border: 1px solid var(--guardian-border-color, #E1E7EF); display: flex; justify-content: center; align-items: center; @@ -326,9 +326,9 @@ right: 0px; width: 400px; z-index: 3; - background: #fff; - border-left: 1px solid #E1E7EF; - box-shadow: 0px 0px 4px 0px #00000014; + 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; @@ -348,10 +348,14 @@ max-width: 32px; position: absolute; padding: 4px; - border-radius: 100%; + border-radius: 6px; cursor: pointer; top: 17px; right: 16px; + + &:hover { + background: var(--guardian-primary-background); + } } .fields-container { @@ -367,7 +371,7 @@ font-weight: 600; line-height: 18px; text-align: left; - color: #23252E; + color: var(--guardian-font-color, #23252E); position: relative; } @@ -492,7 +496,7 @@ font-weight: 700; line-height: 18px; text-align: left; - color: #000000; + color: var(--guardian-font-color, #23252E); width: 100%; padding-bottom: 16px; } @@ -524,7 +528,7 @@ font-weight: 500; line-height: 16px; text-align: left; - color: #23252E; + color: var(--guardian-font-color, #23252E); cursor: pointer; } } @@ -545,14 +549,14 @@ overflow: auto; .variables-container { - box-shadow: 0px 4px 8px 0px #00000014; - background: #FFFFFF; + box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); + background: var(--guardian-background, #FFFFFF); padding: 24px; border-radius: 8px; margin-bottom: 16px; .variables-header { - color: #181818; + color: var(--guardian-font-color, #23252E); font-size: 16px; font-weight: 600; line-height: 20px; @@ -564,7 +568,7 @@ font-family: Inter; font-size: 14px; font-weight: 400; - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); height: 87px; display: flex; justify-content: center; @@ -634,7 +638,17 @@ } .cell-multiselect { - padding: 3px 10px !important; + padding: 1px 10px !important; + } + + .cell-select { + padding: 1px 1px !important; + + &::ng-deep { + .p-dropdown-label { + padding-left: 16px; + } + } } .variables-grid-cell { @@ -687,7 +701,7 @@ padding: 0px 8px; &>div { - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); font-family: Inter; font-size: 12px; font-weight: 400; @@ -696,18 +710,18 @@ } .variables-grid-body { - border: 1px solid #E1E7EF; + 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 #E1E7EF; + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); padding: 0px 8px; &>div { - border-right: 1px solid #E1E7EF; + border-right: 1px solid var(--guardian-border-color, #E1E7EF); &:last-child { border-right: none; @@ -737,13 +751,13 @@ height: 30px; padding: 7px 8px 7px 8px; border-radius: 6px; - background: #EFF3F7; + background: var(--guardian-grey-color, #EFF3F7); font-family: Inter; font-size: 14px; font-weight: 400; line-height: 16px; text-align: left; - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); margin-bottom: 8px; overflow: hidden; white-space: nowrap; @@ -781,9 +795,9 @@ display: flex; .tree-node { - background: #ffffff; + background: var(--guardian-background, #FFFFFF); border: 1px solid #bac0ce; - box-shadow: 0px 4px 4px 0px #00000014; + box-shadow: 0px 4px 4px 0px var(--guardian-shadow, #00000014); border-radius: 6px; width: 150px; cursor: pointer; @@ -830,7 +844,7 @@ .node-header { width: 100%; padding: 9px 8px 9px 16px; - background: #F9FAFC; + background: var(--guardian-grey-background, #F9FAFC); font-family: Inter; font-size: 12px; font-weight: 600; @@ -840,7 +854,7 @@ } .node-fields { - border-top: 1px solid #E1E7EF; + border-top: 1px solid var(--guardian-border-color, #E1E7EF); font-family: Inter; font-size: 12px; font-weight: 500; @@ -852,7 +866,7 @@ .node-field { color: #23252E; - border-bottom: 1px solid #E1E7EF; + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); padding: 8px 8px 8px 16px; overflow: hidden; text-overflow: ellipsis; @@ -860,8 +874,8 @@ } .node-not-fields { - color: #848FA9; - border-bottom: 1px solid #E1E7EF; + color: var(--guardian-disabled-color, #848FA9); + border-bottom: 1px solid var(--guardian-border-color, #E1E7EF); padding: 8px 8px 8px 16px; overflow: hidden; text-overflow: ellipsis; @@ -907,13 +921,13 @@ height: 30px; padding: 7px 8px 7px 8px; border-radius: 6px; - background: #EFF3F7; + background: var(--guardian-grey-color, #EFF3F7); font-family: Inter; font-size: 14px; font-weight: 400; line-height: 16px; text-align: left; - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); margin-bottom: 8px; overflow: hidden; white-space: nowrap; diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss index 474e216bf3..0443d14a4b 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.scss @@ -1,5 +1,5 @@ .policy-name { - color: #848FA9; + color: var(--guardian-disabled-color, #848FA9); font-size: 14px; font-weight: 500; line-height: 16px; diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts index 9747ab69e1..412a69cf8e 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -82,19 +82,19 @@ export class StatisticDefinitionsComponent implements OnInit { id: 'topicId', title: 'Topic', type: 'text', - size: 'auto', + size: '135', tooltip: false }, { id: 'status', title: 'Status', type: 'text', - size: 'auto', + size: '180', tooltip: false }, { id: 'documents', title: 'Documents', type: 'text', - size: 'auto', + size: '125', tooltip: false }, { id: 'edit', @@ -172,7 +172,7 @@ export class StatisticDefinitionsComponent implements OnInit { private loadData() { const filters: any = {}; if (this.currentPolicy?.instanceTopicId) { - filters.topicId = this.currentPolicy?.instanceTopicId; + filters.policyInstanceTopicId = this.currentPolicy?.instanceTopicId; } this.loading = true; this.policyStatisticsService diff --git a/frontend/src/app/themes/guardian/dialog.scss b/frontend/src/app/themes/guardian/dialog.scss index c3120c7241..08b0bda4ea 100644 --- a/frontend/src/app/themes/guardian/dialog.scss +++ b/frontend/src/app/themes/guardian/dialog.scss @@ -2,11 +2,11 @@ border-radius: 16px; .p-dialog-content { - border-top-left-radius: 16px; - border-top-right-radius: 16px; - border-bottom-left-radius: 16px; - border-bottom-right-radius: 16px; - padding: 0 32px 32px 32px; + 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 { diff --git a/frontend/src/app/themes/guardian/grid.scss b/frontend/src/app/themes/guardian/grid.scss index e01c51b260..ea61b6a059 100644 --- a/frontend/src/app/themes/guardian/grid.scss +++ b/frontend/src/app/themes/guardian/grid.scss @@ -3,13 +3,19 @@ $sizes: ( 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, @@ -33,7 +39,7 @@ $sizes: ( } .col-auto { - width: 100%; + width: auto; } @each $name, $size in $sizes { @@ -197,6 +203,25 @@ $sizes: ( 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); + } } } @@ -209,12 +234,18 @@ $sizes: ( position: absolute; left: -1px; right: -1px; - top: 45px; + top: 47px; bottom: -1px; pointer-events: none; - border: 1px solid #00000014; + border: 1px solid var(--guardian-shadow, #00000014); border-radius: 8px; - box-shadow: 0px 4px 8px 0px #00000014; + 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 { @@ -231,6 +262,7 @@ $sizes: ( padding: 16px; border: none; background: transparent; + background-color: transparent; position: relative; &:first-child { @@ -308,7 +340,7 @@ $sizes: ( } .col-auto { - width: 100%; + width: auto; } &.grid-scroll { @@ -344,12 +376,14 @@ $sizes: ( } .p-datatable-thead { - background: #fff; + 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/frontend/src/app/themes/guardian/variables.scss b/frontend/src/app/themes/guardian/variables.scss index 8e9da6a18b..839d039dd1 100644 --- a/frontend/src/app/themes/guardian/variables.scss +++ b/frontend/src/app/themes/guardian/variables.scss @@ -4,6 +4,9 @@ --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; @@ -34,6 +37,8 @@ --guardian-border-color: #E1E7EF; --guardian-close-color: #848FA9; + --guardian-shadow: #00000014; + --guardian-small-font-size: 12px; --guardian-primary-font-size: 14px; diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index a2b301bad1..8770fda8d1 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -66,7 +66,7 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Invalid parameters.'); } const { filters, owner } = msg; - const { pageIndex, pageSize } = filters; + const { policyInstanceTopicId, pageIndex, pageSize } = filters; const otherOptions: any = {}; const _pageSize = parseInt(pageSize, 10); @@ -90,15 +90,16 @@ export async function statisticsAPI(logger: PinoLogger): Promise { 'messageId', 'policyId' ]; - const [items, count] = await DatabaseServer.getStatisticsAndCount( - { - $or: [ - { status: EntityStatus.PUBLISHED }, - { creator: owner.creator } - ] - } as any, - otherOptions - ); + const query: any = { + $or: [ + { status: EntityStatus.PUBLISHED }, + { creator: owner.creator } + ] + }; + if (policyInstanceTopicId) { + query.policyInstanceTopicId = policyInstanceTopicId; + } + const [items, count] = await DatabaseServer.getStatisticsAndCount(query, otherOptions); for (const item of items) { (item as any).documents = await DatabaseServer.getStatisticAssessmentCount({ definitionId: item.id, From 4706c67900251feeb2920292d9d5e53349a10a7b Mon Sep 17 00:00:00 2001 From: envision-ci-agent Date: Fri, 4 Oct 2024 13:45:02 +0000 Subject: [PATCH 38/48] [skip ci] Add swagger.yaml --- swagger.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 31a962abd7..5361fe443d 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -12033,6 +12033,13 @@ paths: example: 20 schema: type: number + - name: policyInstanceTopicId + required: false + in: query + description: Policy Instance Topic Id + example: 0.0.1 + schema: + type: string responses: '200': description: Successful operation. From 3fa8134b8728f12e1cde09cda7229296b85440f3 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 7 Oct 2024 17:13:55 +0400 Subject: [PATCH 39/48] fix Signed-off-by: Stepan Kiryakov --- frontend/src/app/themes/guardian/grid.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/themes/guardian/grid.scss b/frontend/src/app/themes/guardian/grid.scss index ea61b6a059..235b29b249 100644 --- a/frontend/src/app/themes/guardian/grid.scss +++ b/frontend/src/app/themes/guardian/grid.scss @@ -39,7 +39,7 @@ $sizes: ( } .col-auto { - width: auto; + width: 100%; } @each $name, $size in $sizes { From 17d59e051d68dc9615c43130c78764b4806a41d3 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Mon, 7 Oct 2024 18:20:15 +0400 Subject: [PATCH 40/48] fix Signed-off-by: Stepan Kiryakov --- frontend/package-lock.json | 112 ++++++++++++++++++++++++++++++++++++- frontend/src/styles.scss | 14 ++--- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4e7398b50d..e3745ee2f8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@angular/router": "^15.2.10", "@ctrl/ngx-codemirror": "^5.1.1", "@enonic/global-polyfill": "^1.0.0", + "@formulajs/formulajs": "^4.4.6", "@guardian/interfaces": "file:../interfaces", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -35,6 +36,7 @@ "file-saver": "^2.0.5", "js-yaml": "^4.1.0", "leader-line": "^1.0.7", + "mathjs": "^13.1.1", "moment": "^2.29.2", "moment-timezone": "^0.5.43", "ngx-colors": "3.1.4", @@ -69,6 +71,7 @@ } }, "../interfaces": { + "name": "@guardian/interfaces", "version": "2.27.1", "license": "Apache-2.0", "dependencies": { @@ -2938,6 +2941,18 @@ "node": ">=12" } }, + "node_modules/@formulajs/formulajs": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@formulajs/formulajs/-/formulajs-4.4.6.tgz", + "integrity": "sha512-9wzWxMUFNW4RF6o0xJ7KM+oyyXUWKEErvn5c9Gu/Man6cobpG8svYeAvWiWXaT+c8dDa1erUdfH3Amd8oI9xPA==", + "dependencies": { + "bessel": "^1.0.2", + "jstat": "^1.9.6" + }, + "bin": { + "formulajs": "bin/cli.js" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -5238,6 +5253,14 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/bessel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bessel/-/bessel-1.0.2.tgz", + "integrity": "sha512-Al3nHGQGqDYqqinXhQzmwmcRToe/3WyBv4N8aZc5Pef8xw2neZlR9VPi84Sa23JtgWcucu18HxVZrnI0fn2etw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5798,6 +5821,18 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/complex.js": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.2.3.tgz", + "integrity": "sha512-XnOksGXxhQTvL3LjUgwiOPqL7vF7uikCQE/jpuylNpXmG2LZ+l0z1t6qIlJ2TJVDteXPHhlYd3+mhHOGeTFfsg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -6371,6 +6406,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -6868,6 +6908,11 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -7358,7 +7403,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, "engines": { "node": "*" }, @@ -8545,6 +8589,11 @@ "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", "dev": true }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -8665,6 +8714,11 @@ "node >= 0.2.0" ] }, + "node_modules/jstat": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.6.tgz", + "integrity": "sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug==" + }, "node_modules/karma": { "version": "6.3.20", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", @@ -9392,6 +9446,44 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mathjs": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-13.2.0.tgz", + "integrity": "sha512-P5PZoiUX2Tkghkv3tsSqlK0B9My/ErKapv1j6wdxd0MOrYQ30cnGE4LH/kzYB2gA5rN46Njqc4cFgJjaxgijoQ==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.3.7", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mathjs/node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/mathjs/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -12022,6 +12114,11 @@ } } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -13120,6 +13217,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13291,6 +13393,14 @@ "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", "dev": true }, + "node_modules/typed-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "engines": { + "node": ">= 18" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 3a4ce70492..b07ff51ad9 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -1,11 +1,11 @@ /* You can add global styles to this file, and also import other style files */ -@import './variables.scss'; -@import '~codemirror/lib/codemirror'; -@import '~codemirror/theme/material'; -@import '~codemirror/addon/fold/foldgutter.css'; -@import '~codemirror/addon/lint/lint.css'; -@import '~codemirror/addon/hint/show-hint.css'; -@import "~primeicons/primeicons.css"; +@import "./variables.scss"; +@import "codemirror/lib/codemirror"; +@import "codemirror/theme/material"; +@import "codemirror/addon/fold/foldgutter.css"; +@import "codemirror/addon/lint/lint.css"; +@import "codemirror/addon/hint/show-hint.css"; +@import "primeicons/primeicons.css"; @import url("https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined"); html, From 03188d8b1a0bbab66f45e662c9903c925d4e5932 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 8 Oct 2024 14:58:36 +0400 Subject: [PATCH 41/48] fix Signed-off-by: Stepan Kiryakov --- .../common/common-components.module.ts | 7 ++- .../delete-dialog.component.html | 31 +++++++++++ .../delete-dialog.component.scss | 55 +++++++++++++++++++ .../delete-dialog/delete-dialog.component.ts | 33 +++++++++++ ...ew-policy-statistics-dialog.component.scss | 1 + ...stic-definition-configuration.component.ts | 33 ++++++++++- .../statistic-definitions.component.ts | 30 +++++++--- frontend/src/app/themes/guardian/button.scss | 1 + 8 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html create mode 100644 frontend/src/app/modules/common/delete-dialog/delete-dialog.component.scss create mode 100644 frontend/src/app/modules/common/delete-dialog/delete-dialog.component.ts diff --git a/frontend/src/app/modules/common/common-components.module.ts b/frontend/src/app/modules/common/common-components.module.ts index 8adab2f29c..43c4e79f88 100644 --- a/frontend/src/app/modules/common/common-components.module.ts +++ b/frontend/src/app/modules/common/common-components.module.ts @@ -33,6 +33,7 @@ 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 { DeleteDialogComponent } from './delete-dialog/delete-dialog.component'; @NgModule({ declarations: [ @@ -56,7 +57,8 @@ import { StatusDropdown } from './status-dropdown/status-dropdown.component'; CompareViewerComponent, AlertComponent, PaginatorComponent, - StatusDropdown + StatusDropdown, + DeleteDialogComponent ], imports: [ CommonModule, @@ -97,7 +99,8 @@ import { StatusDropdown } from './status-dropdown/status-dropdown.component'; CompareViewerComponent, PaginatorComponent, DataInputDialogComponent, - StatusDropdown + StatusDropdown, + DeleteDialogComponent ] }) export class CommonComponentsModule { } diff --git a/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html new file mode 100644 index 0000000000..0541ea22ca --- /dev/null +++ b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html @@ -0,0 +1,31 @@ +
+
+
{{ header }}
+
+
+ +
+
+ +
+
+
+
+ +
{{ text }}
+
+ + \ No newline at end of file diff --git a/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.scss b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.scss new file mode 100644 index 0000000000..9a84ceec1a --- /dev/null +++ b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.scss @@ -0,0 +1,55 @@ +.loading { + background: #fff; + position: absolute; + z-index: 99; + border-radius: 16px; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; +} + +.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-body { + min-height: 200px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.confirmation-text { + font-family: Inter, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 18px; + margin-top: 25px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.ts b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.ts new file mode 100644 index 0000000000..76387d79e3 --- /dev/null +++ b/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'app-delete-dialog', + templateUrl: './delete-dialog.component.html', + styleUrls: ['./delete-dialog.component.scss'], +}) +export class DeleteDialogComponent implements OnInit { + public loading = true; + public header: string; + public text: string; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig + ) { + this.header = this.config.data.header; + this.text = this.config.data.text; + } + + ngOnInit() { + this.loading = false; + } + + onClose(): void { + this.ref.close(false); + } + + oneDelete() { + this.ref.close(true); + } +} diff --git a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss index 04bdce407c..2c8009ba8d 100644 --- a/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss +++ b/frontend/src/app/modules/policy-statistics/dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component.scss @@ -37,6 +37,7 @@ form { .action-buttons { display: flex; justify-content: flex-end; + user-select: none; &>div { margin-left: 15px; diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts index 7a305d2627..e4627c097a 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.ts @@ -19,6 +19,7 @@ import { DialogService } from 'primeng/dynamicdialog'; import { ScoreDialog } from '../dialogs/score-dialog/score-dialog.component'; import { SchemaRule, SchemaRules } from '../models/schema-rules'; import { StatisticPreviewDialog } from '../dialogs/statistic-preview-dialog/statistic-preview-dialog.component'; +import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component'; @Component({ selector: 'app-statistic-definition-configuration', @@ -499,7 +500,20 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onDeleteVariable(formula: any) { - this.formulas.delete(formula); + const dialogRef = this.dialogService.open(DeleteDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete formula', + text: 'Are you sure want to delete formula?' + }, + }); + dialogRef.onClose.subscribe((result) => { + if (result) { + this.formulas.delete(formula); + } + }); } public onAddScore() { @@ -508,8 +522,21 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onDeleteScore(score: SchemaScore) { - this.scores.delete(score); - this.updateCodeMirror(); + const dialogRef = this.dialogService.open(DeleteDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete score', + text: 'Are you sure want to delete score?' + }, + }); + dialogRef.onClose.subscribe((result) => { + if (result) { + this.scores.delete(score); + this.updateCodeMirror(); + } + }); } public onEditScore(score: SchemaScore) { diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts index 412a69cf8e..035c9ed804 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -7,6 +7,7 @@ import { PolicyStatisticsService } from 'src/app/services/policy-statistics.serv import { ProfileService } from 'src/app/services/profile.service'; import { DialogService } from 'primeng/dynamicdialog'; import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; +import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component'; interface IColumn { id: string; @@ -274,13 +275,26 @@ export class StatisticDefinitionsComponent implements OnInit { } public onDelete(item: any) { - this.loading = true; - this.policyStatisticsService - .deleteDefinition(item) - .subscribe((newItem) => { - this.loadData(); - }, (e) => { - this.loading = false; - }); + const dialogRef = this.dialogService.open(DeleteDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete Statistic', + text: 'Are you sure want to delete statistic?' + }, + }); + dialogRef.onClose.subscribe((result) => { + if (result) { + this.loading = true; + this.policyStatisticsService + .deleteDefinition(item) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); } } \ No newline at end of file diff --git a/frontend/src/app/themes/guardian/button.scss b/frontend/src/app/themes/guardian/button.scss index 6af1abdee3..ca67a01b98 100644 --- a/frontend/src/app/themes/guardian/button.scss +++ b/frontend/src/app/themes/guardian/button.scss @@ -15,6 +15,7 @@ cursor: pointer; border: 1px solid transparent; text-overflow: ellipsis; + outline: none !important; &:hover { filter: brightness(0.95); From 26c52afedf9911a54ef15ec3c6ce7563b08e7e57 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 8 Oct 2024 17:18:11 +0400 Subject: [PATCH 42/48] fix Signed-off-by: Stepan Kiryakov --- .../common/common-components.module.ts | 6 +- .../custom-confirm-dialog.component.html} | 13 +-- .../custom-confirm-dialog.component.scss} | 7 ++ .../custom-confirm-dialog.component.ts} | 21 ++--- .../status-dropdown.component.html | 4 +- .../status-dropdown.component.ts | 1 + ...ic-definition-configuration.component.html | 4 +- ...stic-definition-configuration.component.ts | 39 ++++++--- .../statistic-definitions.component.html | 3 +- .../statistic-definitions.component.ts | 79 +++++++++++++++---- .../api/helpers/policy-statistics-helpers.ts | 13 ++- .../src/api/policy-statistics.service.ts | 10 ++- 12 files changed, 142 insertions(+), 58 deletions(-) rename frontend/src/app/modules/common/{delete-dialog/delete-dialog.component.html => custom-confirm-dialog/custom-confirm-dialog.component.html} (57%) rename frontend/src/app/modules/common/{delete-dialog/delete-dialog.component.scss => custom-confirm-dialog/custom-confirm-dialog.component.scss} (87%) rename frontend/src/app/modules/common/{delete-dialog/delete-dialog.component.ts => custom-confirm-dialog/custom-confirm-dialog.component.ts} (54%) diff --git a/frontend/src/app/modules/common/common-components.module.ts b/frontend/src/app/modules/common/common-components.module.ts index 43c4e79f88..c344167f32 100644 --- a/frontend/src/app/modules/common/common-components.module.ts +++ b/frontend/src/app/modules/common/common-components.module.ts @@ -33,7 +33,7 @@ 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 { DeleteDialogComponent } from './delete-dialog/delete-dialog.component'; +import { CustomCustomDialogComponent } from './custom-confirm-dialog/custom-confirm-dialog.component'; @NgModule({ declarations: [ @@ -58,7 +58,7 @@ import { DeleteDialogComponent } from './delete-dialog/delete-dialog.component'; AlertComponent, PaginatorComponent, StatusDropdown, - DeleteDialogComponent + CustomCustomDialogComponent ], imports: [ CommonModule, @@ -100,7 +100,7 @@ import { DeleteDialogComponent } from './delete-dialog/delete-dialog.component'; PaginatorComponent, DataInputDialogComponent, StatusDropdown, - DeleteDialogComponent + CustomCustomDialogComponent ] }) export class CommonComponentsModule { } diff --git a/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html b/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.html similarity index 57% rename from frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html rename to frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.html index 0541ea22ca..ba3fc9d24e 100644 --- a/frontend/src/app/modules/common/delete-dialog/delete-dialog.component.html +++ b/frontend/src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component.html @@ -2,7 +2,7 @@
{{ header }}
-
+
@@ -17,15 +17,10 @@ -
+
{{label}}
\ No newline at end of file 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 19d7acb5a3..32bb79b6ab 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 @@ -18,6 +18,7 @@ interface IOptions { export class StatusDropdown { @Input('options') options!: IOptions[]; @Input('value') value!: string; + @Input('status') status!: string; @Output('valueChange') valueChange = new EventEmitter(); @Output('change') change = new EventEmitter(); diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index 37d74b6cb7..cf19dff077 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -251,7 +251,7 @@ >
-
+
-
+
) { - if (this.readonly) { + if (this.readonly || field.expandable) { return; } field.selected = !field.selected; @@ -500,17 +501,24 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onDeleteVariable(formula: any) { - const dialogRef = this.dialogService.open(DeleteDialogComponent, { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { showHeader: false, width: '640px', styleClass: 'guardian-dialog', data: { header: 'Delete formula', - text: 'Are you sure want to delete formula?' + text: 'Are you sure want to delete formula?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] }, }); - dialogRef.onClose.subscribe((result) => { - if (result) { + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { this.formulas.delete(formula); } }); @@ -522,17 +530,24 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onDeleteScore(score: SchemaScore) { - const dialogRef = this.dialogService.open(DeleteDialogComponent, { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { showHeader: false, width: '640px', styleClass: 'guardian-dialog', data: { header: 'Delete score', - text: 'Are you sure want to delete score?' + text: 'Are you sure want to delete score?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] }, }); - dialogRef.onClose.subscribe((result) => { - if (result) { + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { this.scores.delete(score); this.updateCodeMirror(); } @@ -568,7 +583,7 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { public onSave() { this.loading = true; - this.rules.update(this.variables); + // this.rules.update(this.variables); const value = this.overviewForm.value; const config = { variables: this.variables.getJson(), @@ -597,7 +612,7 @@ export class StatisticDefinitionConfigurationComponent implements OnInit { } public onPreview() { - this.rules.update(this.variables); + // this.rules.update(this.variables); const value = this.overviewForm.value; const config = { variables: this.variables.getJson(), diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html index 9711ad0d89..b68d88988a 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.html @@ -132,7 +132,8 @@ diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts index 035c9ed804..48fbdef03c 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -1,13 +1,14 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { EntityStatus, UserPermissions } from '@guardian/interfaces'; +import { EntityStatus, IStatistic, 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'; import { NewPolicyStatisticsDialog } from '../dialogs/new-policy-statistics-dialog/new-policy-statistics-dialog.component'; -import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component'; +import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; + interface IColumn { id: string; @@ -256,14 +257,7 @@ export class StatisticDefinitionsComponent implements OnInit { } public onChangeStatus($event: string, row: any): void { - this.loading = true; - this.policyStatisticsService - .publishDefinition(row) - .subscribe((response) => { - this.loadData(); - }, (e) => { - this.loading = false; - }); + this.publish(row) } public onCreateInstance(item: any): void { @@ -275,17 +269,24 @@ export class StatisticDefinitionsComponent implements OnInit { } public onDelete(item: any) { - const dialogRef = this.dialogService.open(DeleteDialogComponent, { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { showHeader: false, width: '640px', styleClass: 'guardian-dialog', data: { header: 'Delete Statistic', - text: 'Are you sure want to delete statistic?' + text: 'Are you sure want to delete statistic?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] }, }); - dialogRef.onClose.subscribe((result) => { - if (result) { + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { this.loading = true; this.policyStatisticsService .deleteDefinition(item) @@ -297,4 +298,54 @@ export class StatisticDefinitionsComponent implements OnInit { } }); } + + private publish(row: IStatistic) { + const rules = row?.config?.rules || []; + const main = rules.find((r) => r.type === 'main'); + if (!main) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Statistic', + text: 'Statistics cannot be published. Please select main schema.', + buttons: [{ + name: 'Close', + class: 'secondary' + }] + }, + }); + dialogRef.onClose.subscribe((result) => { }); + } else { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Publish Statistic', + text: 'Are you sure want to publish statistic?', + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Publish', + class: 'primary' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Publish') { + this.loading = true; + this.policyStatisticsService + .publishDefinition(row) + .subscribe((response) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + } } \ No newline at end of file diff --git a/guardian-service/src/api/helpers/policy-statistics-helpers.ts b/guardian-service/src/api/helpers/policy-statistics-helpers.ts index be773d2847..341e127e27 100644 --- a/guardian-service/src/api/helpers/policy-statistics-helpers.ts +++ b/guardian-service/src/api/helpers/policy-statistics-helpers.ts @@ -328,7 +328,7 @@ function validateRules(data?: IRuleData[]): IRuleData[] { return rules; } -export function validateConfig(data: IStatisticConfig): IStatisticConfig { +export function validateConfig(data?: IStatisticConfig): IStatisticConfig { const config: IStatisticConfig = { variables: validateVariables(data?.variables), scores: validateScores(data?.scores), @@ -338,6 +338,17 @@ export function validateConfig(data: IStatisticConfig): IStatisticConfig { return config; } +export function publishConfig(data?: IStatisticConfig): IStatisticConfig { + const rules = data?.rules || []; + const variables = data?.variables || []; + const schemas = new Set(); + for (const variable of variables) { + schemas.add(variable.schemaId); + } + data.rules = rules.filter((r) => schemas.has(r.schemaId)); + return data; +} + export function getSubject(document: VcDocument): any { let credentialSubject: any = document?.document?.credentialSubject; if (Array.isArray(credentialSubject)) { diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index 8770fda8d1..689ee45f67 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -2,7 +2,7 @@ import { ApiResponse } from './helpers/api-response.js'; import { DatabaseServer, MessageAction, MessageError, MessageResponse, MessageServer, PinoLogger, PolicyImportExport, PolicyStatistic, StatisticAssessmentMessage, StatisticMessage, Users } from '@guardian/common'; import { EntityStatus, IOwner, MessageAPI, PolicyType, Schema, SchemaStatus } from '@guardian/interfaces'; import { publishSchema } from './helpers/index.js'; -import { findRelationships, generateSchema, generateVcDocument, getOrCreateTopic, uniqueDocuments, validateConfig } from './helpers/policy-statistics-helpers.js'; +import { findRelationships, generateSchema, generateVcDocument, getOrCreateTopic, publishConfig, uniqueDocuments, validateConfig } from './helpers/policy-statistics-helpers.js'; /** * Connect to the message broker methods of working with statistics. @@ -88,7 +88,8 @@ export async function statisticsAPI(logger: PinoLogger): Promise { 'status', 'topicId', 'messageId', - 'policyId' + 'policyId', + 'config' ]; const query: any = { $or: [ @@ -212,7 +213,6 @@ export async function statisticsAPI(logger: PinoLogger): Promise { item.name = definition.name; item.description = definition.description; item.method = definition.method; - item.config = definition.config; item.config = validateConfig(definition.config); const result = await DatabaseServer.updateStatistic(item); return new MessageResponse(result); @@ -274,6 +274,9 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError(`Item already published.`); } + item.status = EntityStatus.PUBLISHED; + item.config = publishConfig(item.config); + const statMessage = new StatisticMessage(MessageAction.PublishPolicyStatistic); statMessage.setDocument(item); @@ -286,7 +289,6 @@ export async function statisticsAPI(logger: PinoLogger): Promise { item.topicId = topic.topicId; item.messageId = statMessageResult.getId(); - item.status = EntityStatus.PUBLISHED; const schema = await generateSchema(item, owner); await publishSchema(schema, owner, messageServer, MessageAction.PublishSchema); From 3388ef83eee43b95216b38efa56713baebf06195 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 8 Oct 2024 17:29:35 +0400 Subject: [PATCH 43/48] fix Signed-off-by: Stepan Kiryakov --- ...ic-assessment-configuration.component.html | 28 +++++++++++-------- ...ic-assessment-configuration.component.scss | 2 +- ...stic-assessment-configuration.component.ts | 12 +++++--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html index 3c586a051f..6d6e2b4b5f 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html @@ -57,16 +57,20 @@
Preview
-
-
- - + + +
+
+ + +
+
Scores
-
Scores
-
-
+
+ +
- - - + + +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss index c3f4921b3a..14700173dc 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.scss @@ -68,7 +68,7 @@ .guardian-step-container { width: 264px; - height: 337px; + height: fit-content; padding: 8px 0px 8px 0px; border-radius: 8px; box-shadow: 0px 4px 8px 0px var(--guardian-shadow, #00000014); diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index 2e4aca2d8a..d2df33693f 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -365,7 +365,7 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { this.onStep(this.stepIndex - 1); } - public onNextStep1() { + public onNextPreview() { if (!this.document) { return; } @@ -376,14 +376,18 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { this.onStep(1); } - public onNextStep2() { + public onNextScores() { if (!this.document) { return; } - this.onStep(2); + if(this.scores && this.scores.length) { + this.onStep(2); + } else { + this.onNextFinish(); + } } - public onNextStep3() { + public onNextFinish() { if (!this.document) { return; } From c851ab58ecbd626901c218cba6fb270e02176f0b Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Tue, 8 Oct 2024 18:34:36 +0400 Subject: [PATCH 44/48] fix Signed-off-by: Stepan Kiryakov --- .../statistic-definition-configuration.component.scss | 9 +++++---- .../statistic-definitions.component.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss index 625ca60f23..1dba87ea0f 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss @@ -139,7 +139,7 @@ height: 40px; z-index: 2; background: var(--guardian-grey-background, #F9FAFC); - padding: 8px 16px 0px 16px; + padding: 0px 16px 0px 16px; display: flex; flex-direction: row; border-top: 1px solid var(--guardian-border-color, #E1E7EF); @@ -159,6 +159,7 @@ height: 30px; min-width: 70px; padding: 0 5px; + margin-top: 5px; .tree-tabs-nav-left { width: 30px; @@ -220,9 +221,9 @@ color: var(--guardian-font-color, #23252E); border: 1px solid #C4D0E1; border-radius: 6px; - border-bottom-width: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; height: 30px; cursor: pointer; margin-right: 8px; diff --git a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts index 48fbdef03c..56ea822aed 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-definitions/statistic-definitions.component.ts @@ -275,7 +275,7 @@ export class StatisticDefinitionsComponent implements OnInit { styleClass: 'guardian-dialog', data: { header: 'Delete Statistic', - text: 'Are you sure want to delete statistic?', + text: `Are you sure want to delete statistic (${item.name})?`, buttons: [{ name: 'Close', class: 'secondary' @@ -324,7 +324,7 @@ export class StatisticDefinitionsComponent implements OnInit { styleClass: 'guardian-dialog', data: { header: 'Publish Statistic', - text: 'Are you sure want to publish statistic?', + text: `Are you sure want to publish statistic (${row.name})?`, buttons: [{ name: 'Close', class: 'secondary' From f93b1ef82c34c1b150f1c0b64f1e9ed18b9ac7b5 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 9 Oct 2024 15:54:49 +0400 Subject: [PATCH 45/48] fix Signed-off-by: Stepan Kiryakov --- .../models/schema-formulas.ts | 2 +- ...ic-assessment-configuration.component.html | 12 +++--- ...stic-assessment-configuration.component.ts | 38 +++++++++++-------- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts b/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts index 24869077a2..ab4504d56e 100644 --- a/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts +++ b/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts @@ -95,6 +95,6 @@ export class SchemaFormulas { } public getJson(): any[] { - return this.formulas.map((f) => f.getJson()); + return this.formulas.filter((f) => f.description || f.formula).map((f) => f.getJson()); } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html index 6d6e2b4b5f..cbcda80edf 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.html @@ -240,13 +240,13 @@
- {{v}} + {{getVariableValue(v)}}
- {{variable.value}} + {{getVariableValue(variable.value)}}
@@ -266,13 +266,13 @@
- {{v}} + {{getVariableValue(v)}}
- {{variable.value}} + {{getVariableValue(variable.value)}}
@@ -313,13 +313,13 @@
- {{v}} + {{getVariableValue(v)}}
- {{variable.value}} + {{getVariableValue(variable.value)}}
diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index d2df33693f..c90ab28cd2 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -323,7 +323,7 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { } else if (result.length === 1) { return result[0]; } else { - return 'N/A'; + return undefined; } } @@ -337,16 +337,12 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { if (value) { value = value[path[i]] } else { - return ''; + return undefined; } } - if (value) { - return value; - } else { - return ''; - } + return value; } else { - return 'N/A'; + return undefined; } } @@ -380,7 +376,7 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { if (!this.document) { return; } - if(this.scores && this.scores.length) { + if (this.scores && this.scores.length) { this.onStep(2); } else { this.onNextFinish(); @@ -422,12 +418,14 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { for (const formula of this.formulas) { formula.value = this.calcFormula(formula, document); - if (formula.type === 'string') { - formula.value = String(formula.value); - } else { - formula.value = Number(formula.value); + if (formula.value) { + if (formula.type === 'string') { + formula.value = String(formula.value); + } else { + formula.value = Number(formula.value); + } + document[formula.id] = formula.value; } - document[formula.id] = formula.value; } } @@ -453,7 +451,9 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { const document: any = {}; for (const field of this.preview) { - document[field.id] = field.value; + if (field.value !== undefined) { + document[field.id] = field.value; + } } for (const score of this.scores) { const option = score.options.find((o) => o.value === score.value); @@ -486,4 +486,12 @@ export class StatisticAssessmentConfigurationComponent implements OnInit { }; return report; } + + public getVariableValue(value: any): any { + if (value === undefined) { + return 'N/A'; + } else { + return value; + } + } } \ No newline at end of file From da2d5f1915ccb3da57b65e72f9bf8d7b9100c178 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 9 Oct 2024 16:08:44 +0400 Subject: [PATCH 46/48] fix Signed-off-by: Stepan Kiryakov --- common/src/entity/policy-statistic-document.ts | 2 -- common/src/entity/policy-statistic.ts | 2 +- .../hedera-modules/message/statistic-assessment-message.ts | 2 +- guardian-service/src/app.ts | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/src/entity/policy-statistic-document.ts b/common/src/entity/policy-statistic-document.ts index f814197721..6165dbade1 100644 --- a/common/src/entity/policy-statistic-document.ts +++ b/common/src/entity/policy-statistic-document.ts @@ -109,7 +109,6 @@ export class PolicyStatisticDocument extends BaseEntity implements IStatistic { @Property({ nullable: true }) documentFileId?: ObjectId; - /** * Set defaults */ @@ -118,7 +117,6 @@ export class PolicyStatisticDocument extends BaseEntity implements IStatistic { this.uuid = this.uuid || GenerateUUIDv4(); } - /** * Create document */ diff --git a/common/src/entity/policy-statistic.ts b/common/src/entity/policy-statistic.ts index def2290223..da91fbee07 100644 --- a/common/src/entity/policy-statistic.ts +++ b/common/src/entity/policy-statistic.ts @@ -87,7 +87,7 @@ export class PolicyStatistic extends BaseEntity implements IStatistic { index: true }) policyInstanceTopicId?: string; - + /** * Config */ diff --git a/common/src/hedera-modules/message/statistic-assessment-message.ts b/common/src/hedera-modules/message/statistic-assessment-message.ts index a8b9dcfa03..8e7c709041 100644 --- a/common/src/hedera-modules/message/statistic-assessment-message.ts +++ b/common/src/hedera-modules/message/statistic-assessment-message.ts @@ -112,7 +112,7 @@ export class StatisticAssessmentMessage extends Message { * To documents */ public async toDocuments(key: string): Promise { - let document = JSON.stringify(this.document); + const document = JSON.stringify(this.document); const buffer = Buffer.from(document); return [buffer]; } diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index 0a1ef263fa..6678e49c0b 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -49,7 +49,7 @@ import { PolicyModule, PolicyProperty, PolicyRoles, - PolicyStatistic, + PolicyStatistic, PolicyStatisticDocument, PolicyTest, PolicyTool, @@ -451,7 +451,7 @@ Promise.all([ 'policy-discontinue', async () => { const date = new Date(); - const policiesToDiscontunie = await dataBaseServer.find(Policy,{ + const policiesToDiscontunie = await dataBaseServer.find(Policy, { discontinuedDate: { $lte: date }, status: PolicyType.PUBLISH }); From 39ff0288567528abbb189af53039debaf0f94b8c Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Wed, 9 Oct 2024 20:18:03 +0400 Subject: [PATCH 47/48] fix Signed-off-by: Stepan Kiryakov --- .../statistic-assessment-view.component.scss | 1 + ...stic-definition-configuration.component.scss | 1 + .../src/api/policy-statistics.service.ts | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss index b18815747b..16f684e4e1 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-view/statistic-assessment-view.component.scss @@ -120,6 +120,7 @@ min-height: 42px; overflow: hidden; text-overflow: ellipsis; + user-select: text; } .field-value-array { diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss index 1dba87ea0f..3f670167c6 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.scss @@ -663,6 +663,7 @@ font-size: 14px; font-weight: 400; text-align: left; + user-select: text; .cell-focus { display: none; diff --git a/guardian-service/src/api/policy-statistics.service.ts b/guardian-service/src/api/policy-statistics.service.ts index 689ee45f67..42fb504e21 100644 --- a/guardian-service/src/api/policy-statistics.service.ts +++ b/guardian-service/src/api/policy-statistics.service.ts @@ -1,6 +1,6 @@ import { ApiResponse } from './helpers/api-response.js'; import { DatabaseServer, MessageAction, MessageError, MessageResponse, MessageServer, PinoLogger, PolicyImportExport, PolicyStatistic, StatisticAssessmentMessage, StatisticMessage, Users } from '@guardian/common'; -import { EntityStatus, IOwner, MessageAPI, PolicyType, Schema, SchemaStatus } from '@guardian/interfaces'; +import { EntityStatus, IOwner, MessageAPI, PolicyType, Schema, SchemaEntity, SchemaStatus } from '@guardian/interfaces'; import { publishSchema } from './helpers/index.js'; import { findRelationships, generateSchema, generateVcDocument, getOrCreateTopic, publishConfig, uniqueDocuments, validateConfig } from './helpers/policy-statistics-helpers.js'; @@ -163,7 +163,9 @@ export async function statisticsAPI(logger: PinoLogger): Promise { return new MessageError('Item does not exist.'); } const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); - const all = [].concat(schemas, toolSchemas).filter((s) => s.status === SchemaStatus.PUBLISHED); + const all = [] + .concat(schemas, toolSchemas) + .filter((s) => s.status === SchemaStatus.PUBLISHED && s.entity !== 'EVC'); if (item.status === EntityStatus.PUBLISHED) { const schema = await DatabaseServer.getSchema({ topicId: item.topicId }); return new MessageResponse({ @@ -343,7 +345,16 @@ export async function statisticsAPI(logger: PinoLogger): Promise { } const policyId: string = item.policyId; - const rules = item.config?.rules || []; + let rules = item.config?.rules || []; + + const schemas = await DatabaseServer.getSchemas({ + topicId: item.policyTopicId, + entity: { $nin: [SchemaEntity.EVC] } + }); + const schemasMap = new Set(schemas.map((s) => s.iri)); + + rules = rules.filter((r) => schemasMap.has(r.schemaId)); + const targets = rules.filter((r) => r.type === 'main'); const sub = rules.filter((r) => r.type === 'related'); const all = rules.filter((r) => r.type === 'unrelated'); From 3acf6948d6416395bdb9fb0129d74d47ecc001a3 Mon Sep 17 00:00:00 2001 From: Stepan Kiryakov Date: Thu, 10 Oct 2024 15:00:30 +0400 Subject: [PATCH 48/48] fix Signed-off-by: Stepan Kiryakov --- .../policy-statistics/models/schema-formulas.ts | 2 +- ...statistic-definition-configuration.component.html | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts b/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts index ab4504d56e..e86d6ed349 100644 --- a/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts +++ b/frontend/src/app/modules/policy-statistics/models/schema-formulas.ts @@ -46,7 +46,7 @@ export class SchemaFormulas { public setDefault() { this.names.clear(); this.startIndex = 1; - this.add(); + // this.add(); } public getName(): string { diff --git a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html index cf19dff077..3ec5dc8dff 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html +++ b/frontend/src/app/modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component.html @@ -594,7 +594,7 @@
Output Fields
-
+
ID
TYPE
@@ -658,6 +658,16 @@
+
+
+ + +
+
There were no formulas created yet.
+