diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index d075ee2c8c27845337c2f8c3acb1faa5dcabcca8..05debf8ebc86740ce995dd75db4732464a2682d3 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -166,6 +166,28 @@ import { } from "./common/dialogs/document-property-dialog/document-property-dialog.component"; import {NgxTranslateModule} from "./translate/translate.module"; import {WindowSpinnerService} from "./common/services/window-spinner.service"; +import { + ExpandablePanelComponent +} from "./common/panels/expandable-panel-component/expandable-panel.component"; +import { + ExpandableItemComponent +} from "./common/panels/expandable-panel-component/expandable-item-component/expandable-item.component"; +import { + DocumentEventsPanelComponent +} from "./common/panels/document-events-panel/document-events-panel.component"; +import { + DocumentVersionsPanelComponent +} from "./common/panels/document-versions-panel/document-versions-panel.component"; +import {ReviewTasksComponent} from "./edit/review-task/review-tasks.component"; +import { + ReviewTasksPanelComponent +} from "./common/panels/review-tasks-panel/review-tasks-panel.component"; +import { + DocumentEditPanelComponent +} from "./common/panels/document-edit-panel/document-edit-panel.component"; +import { + ReviewDocumentPanelComponent +} from "./common/panels/review-tasks-panel/review-document-panel/review-document-panel.component"; @NgModule({ @@ -198,8 +220,11 @@ import {WindowSpinnerService} from "./common/services/window-spinner.service"; DnsToolsComponent, DnsQueryPanelComponent, DocumentWizardDialogComponent, + DocumentEditPanelComponent, + DocumentEventsPanelComponent, DocumentPropertiesPanelComponent, DocumentPropertyDialogComponent, + DocumentVersionsPanelComponent, DomainGroupComponent, DomainPanelComponent, DomainResourceTypePanelComponent, @@ -209,6 +234,8 @@ import {WindowSpinnerService} from "./common/services/window-spinner.service"; EditDomainComponent, EditGroupComponent, EditResourceComponent, + ExpandablePanelComponent, + ExpandableItemComponent, ExpiredPasswordDialogComponent, ExtensionComponent, ExtensionPanelComponent, @@ -236,6 +263,9 @@ import {WindowSpinnerService} from "./common/services/window-spinner.service"; RowLimiterComponent, SaveDialogComponent, SearchTableComponent, + ReviewDocumentPanelComponent, + ReviewTasksComponent, + ReviewTasksPanelComponent, ResourceSearchComponent, SidenavComponent, SmpFieldErrorComponent, diff --git a/smp-angular/src/app/app.routes.ts b/smp-angular/src/app/app.routes.ts index 792415e137392bb2b3354898568dcd33af61a4db..0bfadef582e106903abcb4cb145ca5caf123d07a 100644 --- a/smp-angular/src/app/app.routes.ts +++ b/smp-angular/src/app/app.routes.ts @@ -1,28 +1,69 @@ import {RouterModule, Routes} from '@angular/router'; import {LoginComponent} from './login/login.component'; -import {ResourceSearchComponent} from './resource-search/resource-search.component'; -import {PropertyComponent} from "./system-settings/admin-properties/property.component"; -import {UserProfileComponent} from "./user-settings/user-profile/user-profile.component"; +import { + ResourceSearchComponent +} from './resource-search/resource-search.component'; +import { + PropertyComponent +} from "./system-settings/admin-properties/property.component"; +import { + UserProfileComponent +} from "./user-settings/user-profile/user-profile.component"; import {authenticationGuard} from "./guards/authentication.guard"; -import {UserAccessTokensComponent} from "./user-settings/user-access-tokens/user-access-tokens.component"; -import {UserCertificatesComponent} from "./user-settings/user-certificates/user-certificates.component"; -import {ExtensionComponent} from "./system-settings/admin-extension/extension.component"; -import {AdminTruststoreComponent} from "./system-settings/admin-truststore/admin-truststore.component"; -import {AdminKeystoreComponent} from "./system-settings/admin-keystore/admin-keystore.component"; -import {AdminDomainComponent} from "./system-settings/admin-domain/admin-domain.component"; +import { + UserAccessTokensComponent +} from "./user-settings/user-access-tokens/user-access-tokens.component"; +import { + UserCertificatesComponent +} from "./user-settings/user-certificates/user-certificates.component"; +import { + ExtensionComponent +} from "./system-settings/admin-extension/extension.component"; +import { + AdminTruststoreComponent +} from "./system-settings/admin-truststore/admin-truststore.component"; +import { + AdminKeystoreComponent +} from "./system-settings/admin-keystore/admin-keystore.component"; +import { + AdminDomainComponent +} from "./system-settings/admin-domain/admin-domain.component"; import {dirtyDeactivateGuard} from "./guards/dirty.guard"; -import {AdminUserComponent} from "./system-settings/admin-users/admin-user.component"; +import { + AdminUserComponent +} from "./system-settings/admin-users/admin-user.component"; import {EditDomainComponent} from "./edit/edit-domain/edit-domain.component"; import {EditGroupComponent} from "./edit/edit-group/edit-group.component"; -import {EditResourceComponent} from "./edit/edit-resources/edit-resource.component"; -import {ResourceDocumentPanelComponent} from "./edit/edit-resources/resource-document-panel/resource-document-panel.component"; -import {SubresourceDocumentPanelComponent} from "./edit/edit-resources/subresource-document-panel/subresource-document-panel.component"; -import {authorizeChildSystemAdminGuard} from "./guards/authorize-child-system-admin.guard"; -import {activateChildResourceGuard} from "./guards/activate-child-document.guard"; -import {UserAlertsComponent} from "./user-settings/user-alerts/user-alerts.component"; -import {AdminAlertsComponent} from "./system-settings/admin-alerts/admin-alerts.component"; -import {ResetCredentialComponent} from "./security/reset-credential/reset-credential.component"; +import { + EditResourceComponent +} from "./edit/edit-resources/edit-resource.component"; +import { + ResourceDocumentPanelComponent +} from "./edit/edit-resources/resource-document-panel/resource-document-panel.component"; +import { + SubresourceDocumentPanelComponent +} from "./edit/edit-resources/subresource-document-panel/subresource-document-panel.component"; +import { + authorizeChildSystemAdminGuard +} from "./guards/authorize-child-system-admin.guard"; +import { + activateChildResourceGuard +} from "./guards/activate-child-document.guard"; +import { + UserAlertsComponent +} from "./user-settings/user-alerts/user-alerts.component"; +import { + AdminAlertsComponent +} from "./system-settings/admin-alerts/admin-alerts.component"; +import { + ResetCredentialComponent +} from "./security/reset-credential/reset-credential.component"; import {DnsToolsComponent} from "./tools/dns-tools/dns-tools.component"; +import {ReviewTasksComponent} from "./edit/review-task/review-tasks.component"; +import { + ReviewDocumentPanelComponent +} from "./common/panels/review-tasks-panel/review-document-panel/review-document-panel.component"; +import {activateChildReviewGuard} from "./guards/activate-child-review.guard"; const appRoutes: Routes = [ @@ -36,8 +77,16 @@ const appRoutes: Routes = [ path: 'edit', canActivateChild: [authenticationGuard], children: [ - {path: 'edit-domain', component: EditDomainComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'edit-group', component: EditGroupComponent, canDeactivate: [dirtyDeactivateGuard]}, + { + path: 'edit-domain', + component: EditDomainComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'edit-group', + component: EditGroupComponent, + canDeactivate: [dirtyDeactivateGuard] + }, { path: 'edit-resource', canDeactivate: [dirtyDeactivateGuard], @@ -54,8 +103,23 @@ const appRoutes: Routes = [ component: SubresourceDocumentPanelComponent, canDeactivate: [dirtyDeactivateGuard] }, - {path: '', component: EditResourceComponent, canDeactivate: [dirtyDeactivateGuard]}, + { + path: '', + component: EditResourceComponent, + canDeactivate: [dirtyDeactivateGuard] + }, ] + }, + { + path: 'review-tasks', + children: [ + { + path: 'review-document', + canActivate: [activateChildReviewGuard], + component: ReviewDocumentPanelComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + {path: '', component: ReviewTasksComponent},] } ] }, @@ -63,24 +127,72 @@ const appRoutes: Routes = [ path: 'system-settings', canActivateChild: [authenticationGuard, authorizeChildSystemAdminGuard], children: [ - {path: 'domain', component: AdminDomainComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'user', component: AdminUserComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'properties', component: PropertyComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'keystore', component: AdminKeystoreComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'truststore', component: AdminTruststoreComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'extension', component: ExtensionComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'alert', component: AdminAlertsComponent, canDeactivate: [dirtyDeactivateGuard]}, + { + path: 'domain', + component: AdminDomainComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'user', + component: AdminUserComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'properties', + component: PropertyComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'keystore', + component: AdminKeystoreComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'truststore', + component: AdminTruststoreComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'extension', + component: ExtensionComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'alert', + component: AdminAlertsComponent, + canDeactivate: [dirtyDeactivateGuard] + }, ] }, { path: 'user-settings', canActivateChild: [authenticationGuard], children: [ - {path: 'user-profile', component: UserProfileComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'user-access-token', component: UserAccessTokensComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'user-certificate', component: UserCertificatesComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'user-alert', component: UserAlertsComponent, canDeactivate: [dirtyDeactivateGuard]}, - {path: 'user-membership', component: UserProfileComponent, canDeactivate: [dirtyDeactivateGuard]}, + { + path: 'user-profile', + component: UserProfileComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'user-access-token', + component: UserAccessTokensComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'user-certificate', + component: UserCertificatesComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'user-alert', + component: UserAlertsComponent, + canDeactivate: [dirtyDeactivateGuard] + }, + { + path: 'user-membership', + component: UserProfileComponent, + canDeactivate: [dirtyDeactivateGuard] + }, ] }, {path: '**', redirectTo: ''}, diff --git a/smp-angular/src/app/common/alert-message/alert-message.service.ts b/smp-angular/src/app/common/alert-message/alert-message.service.ts index 95f7ddbb8be69481059ada5c57b24bcf2428b373..1f181edeb43e97c63bb3b58f2099660804820bd0 100644 --- a/smp-angular/src/app/common/alert-message/alert-message.service.ts +++ b/smp-angular/src/app/common/alert-message/alert-message.service.ts @@ -1,7 +1,8 @@ import {Injectable} from '@angular/core'; import {NavigationEnd, NavigationStart, Router} from '@angular/router'; -import {Observable, Subject} from 'rxjs'; +import {lastValueFrom, Observable, Subject} from 'rxjs'; import {HttpErrorResponse} from "@angular/common/http"; +import {TranslateService} from "@ngx-translate/core"; /** * AlertMessageRO is the object that will be used to display the message in the SMP alert component in overlay. @@ -25,7 +26,8 @@ export class AlertMessageService { private keepAfterNavigationChange:boolean = false; private message: AlertMessageRO; - constructor(private router: Router) { + constructor(private router: Router, + private translateService: TranslateService) { // clear alert message on route change router.events.subscribe(event => { if (event instanceof NavigationStart) { @@ -80,7 +82,6 @@ export class AlertMessageService { * @param messageObject */ getObjectMessage(messageObject: any): string { - let message = 'An error occurred'; if (typeof messageObject === 'string') { return messageObject; } @@ -124,6 +125,15 @@ export class AlertMessageService { this.displayCurrentMessage(); } + async showMessageForTranslation(translationCode: string,type: string, keepAfterNavigationChange = false, timeoutInSeconds: number = null) : Promise<void> { + let message = await lastValueFrom(this.translateService.get(translationCode)) + this.showMessage(message, type, keepAfterNavigationChange, timeoutInSeconds); + } + + successForTranslation(translationCode: string, keepAfterNavigationChange = false, timeoutInSeconds: number = null) { + this.showMessageForTranslation(translationCode, 'success', keepAfterNavigationChange, timeoutInSeconds); + } + success(message: string, keepAfterNavigationChange = false, timeoutInSeconds: number = null) { this.showMessage(message, 'success', keepAfterNavigationChange, timeoutInSeconds); } @@ -132,6 +142,10 @@ export class AlertMessageService { this.showMessage(message, 'warning', keepAfterNavigationChange, timeoutInSeconds); } + errorForTranslation(translationCode: string, keepAfterNavigationChange = false, timeoutInSeconds: number = null) { + this.showMessageForTranslation(translationCode, 'error', keepAfterNavigationChange, timeoutInSeconds); + } + error(message: any, keepAfterNavigationChange = false, timeoutInSeconds: number = null) { this.showMessage(message, 'error', keepAfterNavigationChange, timeoutInSeconds); } diff --git a/smp-angular/src/app/common/components/smp-editor/smp-editor.component.ts b/smp-angular/src/app/common/components/smp-editor/smp-editor.component.ts index a3238c96351aa1d8a0579851f92ca28aa5c086bc..953da50d7466d6f2bc139c72cd820a05d2cb043d 100644 --- a/smp-angular/src/app/common/components/smp-editor/smp-editor.component.ts +++ b/smp-angular/src/app/common/components/smp-editor/smp-editor.component.ts @@ -144,6 +144,7 @@ export class SmpEditorComponent @Input() set readOnly(readOnly: boolean) { this._readOnly = readOnly; + console.log("Document readOnly", readOnly) this.codeMirror?.dispatch({ effects: this.readOnlyDocument.reconfigure(EditorState.readOnly.of(readOnly)) }) diff --git a/smp-angular/src/app/common/dialogs/certificate-dialog/certificate-dialog.component.ts b/smp-angular/src/app/common/dialogs/certificate-dialog/certificate-dialog.component.ts index bf632f6075f298e1704ea46f263b900ebe0dc1a0..401696bf64407eda8df6910dfb54722bb510c7f9 100644 --- a/smp-angular/src/app/common/dialogs/certificate-dialog/certificate-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/certificate-dialog/certificate-dialog.component.ts @@ -1,7 +1,6 @@ import {Component, Inject} from '@angular/core'; import {MAT_DIALOG_DATA} from '@angular/material/dialog'; import {UntypedFormBuilder} from "@angular/forms"; -import {SmpConstants} from "../../../smp.constants"; import {CertificateRo} from "../../model/certificate-ro.model"; import {TranslateService} from "@ngx-translate/core"; @@ -10,7 +9,7 @@ import {TranslateService} from "@ngx-translate/core"; templateUrl: './certificate-dialog.component.html' }) export class CertificateDialogComponent { - readonly dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; + formTitle: string; current: CertificateRo; diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts index 19912c3d1cc3895e779ed63b0c49b27afecef470..1deb1ae92a62e22ec0bc58de684096588c0c69d3 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts @@ -1,7 +1,6 @@ import {Component, Inject} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; -import {SmpConstants} from "../../../smp.constants"; import {AccessTokenRo} from "../../model/access-token-ro.model"; import {CredentialRo} from "../../../security/credential.model"; import {HttpErrorHandlerService} from "../../error/http-error-handler.service"; @@ -19,7 +18,6 @@ export class CredentialDialogComponent { public static CERTIFICATE_TYPE: string = "CERTIFICATE"; public static ACCESS_TOKEN_TYPE: string = "ACCESS_TOKEN"; - dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; formTitle: string; credentialForm: FormGroup; certificateForm: FormGroup; diff --git a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.css b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.css index 1c224cec9922cc4489dc8d1c050c783cb15c6255..bcc42673eca6fe33a199cbaf472eafacce461830 100644 --- a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.css +++ b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.css @@ -1,8 +1,11 @@ +.form-field-full-width { + width: 100%; +} + .empty-field-label { color: gray; } - #custom-file-upload { display: none; } @@ -11,3 +14,6 @@ display: inline-block; cursor: pointer; } +#member-user-can-review { + max-height: 1.5em +} diff --git a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.html b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.html index c3b40e2f8f0beb8c99e1de57a515e3d47ef2be12..ab7f34ceb5b818fc63ed83ac7127f3bebe57349e 100644 --- a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.html @@ -2,7 +2,7 @@ <mat-dialog-content style="width:700px"> <form [formGroup]="memberForm"> <b *ngIf="newMode">{{ "member.dialog.label.invite.members" | translate: {target: inviteTarget} }}</b> - <mat-form-field style="width: 100%"> + <mat-form-field class="form-field-full-width"> <mat-label>{{ "member.dialog.label.choose.user" | translate }}</mat-label> <input id="member-user" type="text" matInput formControlName="member-user" [matAutocomplete]="auto" (keyup)="applyUserFilter($event)" @@ -16,9 +16,9 @@ </mat-form-field> - <mat-form-field style="width:100%"> - <mat-label>Select role for the user</mat-label> - <select matNativeControl placeholder="{{ 'member.dialog.placeholder.role.type' | translate }}" + <mat-form-field class="form-field-full-width"> + <mat-label>{{ "member.dialog.label.select.role.type" | translate }}</mat-label> + <select matNativeControl placeholder="{{ 'member.dialog.label.permission.review' | translate }}" formControlName="member-roleType" name="Role type" matTooltip="{{ 'member.dialog.tooltip.role.type' | translate }}" @@ -30,6 +30,17 @@ </select> <mat-hint>{{ "member.dialog.hint.choose.role" | translate }}</mat-hint> </mat-form-field> + + <mat-form-field class="form-field-full-width" > + <mat-checkbox *ngIf="isResourceMember" formControlName="member-can-review" id="member-user-can-review" + matTooltip="{{ 'member.dialog.tooltip.permission.review' | translate }}" + >{{ "member.dialog.label.permission.review" | translate }} + <!-- This input is used to make the mat-checkbox as form filed --> + <input matInput style="display: none;"> + </mat-checkbox> + <mat-hint>{{ "member.dialog.hint.can.review" | translate }}</mat-hint> + </mat-form-field> + </form> </mat-dialog-content> <mat-dialog-actions> diff --git a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts index f0fc3e278e8e7bceb3e57bb9e63b24bf32b7f756..bed709837ab5bd5b8702c3889afa654ef8b23856 100644 --- a/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/member-dialog/member-dialog.component.ts @@ -2,7 +2,7 @@ import {Component, Inject, Input, OnInit} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {MembershipRoleEnum} from "../../enums/membership-role.enum"; -import {firstValueFrom, lastValueFrom, Observable} from "rxjs"; +import {lastValueFrom, Observable} from "rxjs"; import {SearchUserRo} from "../../model/search-user-ro.model"; import {MembershipService} from "../../panels/membership-panel/membership.service"; import {MemberRo} from "../../model/member-ro.model"; @@ -58,7 +58,8 @@ export class MemberDialogComponent implements OnInit { 'member-user': new FormControl({value: null}), 'member-fullName': new FormControl({value: null}), 'member-memberOf': new FormControl({value: null}), - 'member-roleType': new FormControl({value: null}) + 'member-roleType': new FormControl({value: null}), + 'member-can-review': new FormControl({value: null}) }); this.member = { ...data.member @@ -79,6 +80,7 @@ export class MemberDialogComponent implements OnInit { member.fullName = this.memberForm.get('member-fullName').value; member.memberOf = this.memberForm.get('member-memberOf').value; member.roleType = this.memberForm.get('member-roleType').value; + member.hasPermissionReview = this.memberForm.get('member-can-review').value return member; } @@ -102,12 +104,14 @@ export class MemberDialogComponent implements OnInit { this.memberForm.controls['member-fullName'].setValue(value.fullName); this.memberForm.controls['member-memberOf'].setValue(value.memberOf); this.memberForm.controls['member-roleType'].setValue(value.roleType); + this.memberForm.controls['member-can-review'].setValue(value.hasPermissionReview); } else { this.memberForm.controls['member-user'].setValue(""); this.memberForm.controls['member-fullName'].setValue(""); this.memberForm.controls['member-memberOf'].setValue(""); this.memberForm.controls['member-roleType'].setValue(""); + this.memberForm.controls['member-can-review'].setValue(false); } this.memberForm.markAsPristine(); } @@ -173,4 +177,8 @@ export class MemberDialogComponent implements OnInit { return this.membershipService.addEditMemberToResource(this._currentResource, this._currentGroup,this._currentDomain, this.member) } } + + get isResourceMember(): boolean { + return this.membershipType === MemberTypeEnum.RESOURCE; + } } diff --git a/smp-angular/src/app/common/enums/document-versions-status.enum.ts b/smp-angular/src/app/common/enums/document-versions-status.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..b36abd74a66336917fbfa2c6ce36da09257bd2f6 --- /dev/null +++ b/smp-angular/src/app/common/enums/document-versions-status.enum.ts @@ -0,0 +1,14 @@ +/** + * Enum for document versions status codes + * @Since 5.0 + * @Author: Joze RIHTARSIC + */ +export enum DocumentVersionsStatus { + + DRAFT="DRAFT", + PUBLISHED="PUBLISHED", + RETIRED="RETIRED", + UNDER_REVIEW="UNDER_REVIEW", + APPROVED="APPROVED", + REJECTED="REJECTED" +} diff --git a/smp-angular/src/app/common/error/http-error-handler.service.ts b/smp-angular/src/app/common/error/http-error-handler.service.ts index d93cf8a1492810409e5e78fd078d41b309004372..463fbd353d7098ff50f0f68c85c0519a323ab98e 100644 --- a/smp-angular/src/app/common/error/http-error-handler.service.ts +++ b/smp-angular/src/app/common/error/http-error-handler.service.ts @@ -7,8 +7,7 @@ import {AlertMessageService} from "../alert-message/alert-message.service"; export class HttpErrorHandlerService { constructor (private navigationService: NavigationService, - private alertMessageService: AlertMessageService,) { - + private alertMessageService: AlertMessageService) { } public logoutOnInvalidSessionError(err: any): boolean { @@ -21,4 +20,20 @@ export class HttpErrorHandlerService { } return false; } + + public handleHttpError(err: any) { + if (err instanceof HttpErrorResponse) { + if (this.logoutOnInvalidSessionError(err)) { + return; + } + if (err.status === 0) { + this.alertMessageService.error("Server is not reachable. Please try again later."); + } else { + this.alertMessageService.error(err.error.errorDescription); + } + } else { + this.alertMessageService.error(err.error?.errorDescription); + } + + } } diff --git a/smp-angular/src/app/common/global-lookups.ts b/smp-angular/src/app/common/global-lookups.ts index 427d4d92d35e2e76052946647b52d4059337fad9..cec7984f7dd357fa44dd7091a3a92bbd9ce06b79 100644 --- a/smp-angular/src/app/common/global-lookups.ts +++ b/smp-angular/src/app/common/global-lookups.ts @@ -14,6 +14,13 @@ import {DateAdapter} from "@angular/material/core"; import {NgxMatDateAdapter} from "@angular-material-components/datetime-picker"; import {DomainRo} from "./model/domain-ro.model"; import {Subject} from "rxjs"; +import { + FormatWidth, + getLocaleDateFormat, + getLocaleDateTimeFormat, + getLocaleTimeFormat +} from "@angular/common"; +import StringUtils from "./utils/string-utils"; /** * Purpose of object is to fetch lookups as domains and users @@ -24,6 +31,7 @@ export class GlobalLookups { // global data observers. The components will subscribe to these Subject to get // data updates. private smpInfoUpdateSubject: Subject<SmpInfo> = new Subject<SmpInfo>(); + private readonly DEFAULT_LOCALE: string = 'fr'; domainObserver: Observable<SearchTableResult> userObserver: Observable<SearchTableResult> @@ -60,8 +68,8 @@ export class GlobalLookups { } ); // set default locale - dateAdapter.setLocale('fr'); - ngxMatDateAdapter.setLocale('fr'); + dateAdapter.setLocale(this.DEFAULT_LOCALE); + ngxMatDateAdapter.setLocale(this.DEFAULT_LOCALE); } @@ -91,6 +99,31 @@ export class GlobalLookups { }); } + getCurrentLocale(): string { + if (this.securityService.getCurrentUser() == null) { + return this.DEFAULT_LOCALE; + } + return this.securityService.getCurrentUser().smpLocale; + } + + public getDateTimeFormat(withSeconds: boolean = true): string { + let locale = this.getCurrentLocale(); + locale = locale ? locale : this.DEFAULT_LOCALE; + let format: string = getLocaleDateTimeFormat(locale, FormatWidth.Short); + let fullTime = getLocaleTimeFormat(locale,withSeconds? FormatWidth.Medium:FormatWidth.Short); + let fullDate = getLocaleDateFormat(locale, FormatWidth.Short); + let result = StringUtils.format(format, [fullTime, fullDate]); + return result; + } + + private format(str, opt_values) { + if (opt_values) { + str = str.replace(/\{([^}]+)}/g, function (match, key) { + return (opt_values != null && key in opt_values) ? opt_values[key] : match; + }); + } + return str; + } public refreshApplicationInfo() { @@ -114,13 +147,14 @@ export class GlobalLookups { console.log("Refresh application configuration is authenticated " + isAuthenticated) if (isAuthenticated) { this.http.get<SmpConfig>(SmpConstants.REST_PUBLIC_APPLICATION_CONFIG) - .subscribe({next: (res :SmpConfig):void => { - this.cachedApplicationConfig = res; - }, - error: (err: any)=> { - console.log("getSmpConfig:" + err); - } - }); + .subscribe({ + next: (res: SmpConfig): void => { + this.cachedApplicationConfig = res; + }, + error: (err: any) => { + console.log("getSmpConfig:" + err); + } + }); } }); } diff --git a/smp-angular/src/app/common/model/document-ro.model.ts b/smp-angular/src/app/common/model/document-ro.model.ts index cf0a058179bbb01d17fe30b2a2c6864f25314888..c5ec808d8cebbcdbfbe88fbaef0f1ce687d0532f 100644 --- a/smp-angular/src/app/common/model/document-ro.model.ts +++ b/smp-angular/src/app/common/model/document-ro.model.ts @@ -1,6 +1,11 @@ import {DocumentPropertyRo} from "./document-property-ro.model"; import {SearchTableEntity} from "../search-table/search-table-entity.model"; import {EntityStatus} from "../enums/entity-status.enum"; +import { + DocumentVersionRo +} from "./document-version-ro.model"; +import {DocumentVersionEventRo} from "./document-version-event-ro.model"; +import {DocumentVersionsStatus} from "../enums/document-versions-status.enum"; export interface DocumentRo extends SearchTableEntity { mimeType?: string; @@ -13,5 +18,8 @@ export interface DocumentRo extends SearchTableEntity { payload?:string; payloadStatus: EntityStatus; properties?: DocumentPropertyRo[]; + documentVersionStatus?: DocumentVersionsStatus; + documentVersionEvents?: DocumentVersionEventRo[]; + documentVersions?: DocumentVersionRo[]; } diff --git a/smp-angular/src/app/common/model/document-version-event-ro.model.ts b/smp-angular/src/app/common/model/document-version-event-ro.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..17ba3452cb5e9845d821fa7fcadb66eb656afc17 --- /dev/null +++ b/smp-angular/src/app/common/model/document-version-event-ro.model.ts @@ -0,0 +1,10 @@ +import {SearchTableEntity} from "../search-table/search-table-entity.model"; + +export interface DocumentVersionEventRo extends SearchTableEntity { + eventType: string; + eventOn: Date; + username: string; + eventSourceType: string; + details: string; +} + diff --git a/smp-angular/src/app/common/model/document-version-ro.model.ts b/smp-angular/src/app/common/model/document-version-ro.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..bdd3218bdf7d9d450a3bacfecae8c41cef46c5b2 --- /dev/null +++ b/smp-angular/src/app/common/model/document-version-ro.model.ts @@ -0,0 +1,9 @@ +import {SearchTableEntity} from "../search-table/search-table-entity.model"; + +export interface DocumentVersionRo extends SearchTableEntity { + version: number; + versionStatus:string; + createdOn: Date; + lastUpdatedOn: Date; +} + diff --git a/smp-angular/src/app/common/model/member-ro.model.ts b/smp-angular/src/app/common/model/member-ro.model.ts index 618e326fcbab2302486f00f6d78e261b6cac3c89..6d042f0f5f476dd924879f3c6fc8a8b622243199 100644 --- a/smp-angular/src/app/common/model/member-ro.model.ts +++ b/smp-angular/src/app/common/model/member-ro.model.ts @@ -10,4 +10,7 @@ export interface MemberRo extends SearchTableEntity { memberOf:MemberTypeEnum; fullName:string; roleType:MembershipRoleEnum; + + // only for resource members + hasPermissionReview?:boolean; } diff --git a/smp-angular/src/app/common/model/resource-ro.model.ts b/smp-angular/src/app/common/model/resource-ro.model.ts index 11e4cc5408beec91f56a3f384601269b0548bf58..068c333c91d06b8c297a2f9bb98c8f3a8ac984d2 100644 --- a/smp-angular/src/app/common/model/resource-ro.model.ts +++ b/smp-angular/src/app/common/model/resource-ro.model.ts @@ -12,6 +12,7 @@ export interface ResourceRo extends SearchTableEntity { smlRegistered: boolean; + reviewEnabled?: boolean; visibility: VisibilityEnum; } diff --git a/smp-angular/src/app/common/model/review-document-version-ro.model.ts b/smp-angular/src/app/common/model/review-document-version-ro.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6534026f1a4cc195cbda2d145f4f3cf4417945f --- /dev/null +++ b/smp-angular/src/app/common/model/review-document-version-ro.model.ts @@ -0,0 +1,17 @@ +import {SearchTableEntity} from "../search-table/search-table-entity.model"; + +export interface ReviewDocumentVersionRo extends SearchTableEntity { + + documentId: string; + documentVersionId: string; + resourceId: string; + subresourceId?: string; + version: number; + currentStatus: string; + resourceIdentifierValue: string; + resourceIdentifierScheme: string; + subresourceIdentifierValue?: string; + subresourceIdentifierScheme?: string; + target: string; + lastUpdatedOn: Date; +} diff --git a/smp-angular/src/app/common/model/subresource-ro.model.ts b/smp-angular/src/app/common/model/subresource-ro.model.ts index 9fa29080ccaa96d3b6ead3b323096d4e326abbc9..807e40d4bcdd1bab99c65915a95f0e066a0917d1 100644 --- a/smp-angular/src/app/common/model/subresource-ro.model.ts +++ b/smp-angular/src/app/common/model/subresource-ro.model.ts @@ -1,12 +1,9 @@ import {SearchTableEntity} from "../search-table/search-table-entity.model"; -import {VisibilityEnum} from "../enums/visibility.enum"; export interface SubresourceRo extends SearchTableEntity { subresourceId?: string; subresourceTypeIdentifier?: string; - identifierValue: string; - identifierScheme?: string; } diff --git a/smp-angular/src/app/common/panels/alert-panel/alert-panel.component.ts b/smp-angular/src/app/common/panels/alert-panel/alert-panel.component.ts index 9e9dd40d8c13f0c4e25a423e60b93d256d9d39b4..f102462f56351440ef4134a61761bf47f2fb5d5f 100644 --- a/smp-angular/src/app/common/panels/alert-panel/alert-panel.component.ts +++ b/smp-angular/src/app/common/panels/alert-panel/alert-panel.component.ts @@ -2,7 +2,8 @@ import { AfterViewChecked, AfterViewInit, ChangeDetectorRef, - Component, Input, + Component, + Input, OnInit, TemplateRef, ViewChild @@ -13,11 +14,12 @@ import {MatDialog} from '@angular/material/dialog'; import {AlertMessageService} from '../../alert-message/alert-message.service'; import {AlertController} from './alert-controller'; import {HttpClient} from '@angular/common/http'; -import {SmpConstants} from "../../../smp.constants"; import {GlobalLookups} from "../../global-lookups"; import {SearchTableComponent} from "../../search-table/search-table.component"; import {SecurityService} from "../../../security/security.service"; -import {ObjectPropertiesDialogComponent} from "../../dialogs/object-properties-dialog/object-properties-dialog.component"; +import { + ObjectPropertiesDialogComponent +} from "../../dialogs/object-properties-dialog/object-properties-dialog.component"; /** * This is a generic alert panel component for previewing alert list @@ -37,14 +39,10 @@ export class AlertPanelComponent implements OnInit, AfterViewInit, AfterViewChec @ViewChild('credentialType') credentialType: TemplateRef<any>; @ViewChild('forUser') forUser: TemplateRef<any>; - readonly dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; - readonly dateFormat: string = SmpConstants.DATE_FORMAT; - - @Input() baseUrl = null; + @Input() baseUrl = null; columnPicker: ColumnPicker = new ColumnPicker(); alertController: AlertController; filter: any = {}; - isSMPIntegrationOn: boolean = false; constructor(public securityService: SecurityService, protected lookups: GlobalLookups, @@ -142,4 +140,8 @@ export class AlertPanelComponent implements OnInit, AfterViewInit, AfterViewChec isDirty(): boolean { return this.searchTable.isDirty(); } + + get dateTimeFormat(): string { + return this.lookups.getDateTimeFormat(); + } } diff --git a/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.html b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..7d74a66f4be763ecc472bb24ee13f39e4c5847c5 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.html @@ -0,0 +1,201 @@ +<div id="resource-document-panel"> + + <mat-toolbar class="mat-elevation-z2" style="min-height: 50px !important;"> + <mat-toolbar-row class="smp-toolbar-row" + style="justify-content: space-between;min-height: 50px !important;"> + <button id="newVersion_id" + *ngIf="isNotReviewMode" + mat-raised-button + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.version.new' | translate }}" + [disabled]="emptyDocument" + (click)="onNewDocumentVersionButtonClicked()"> + <mat-icon>add_circle</mat-icon> + <span>{{ "document.edit.panel.button.version.new" | translate }}</span> + </button> + <button id="validateResource_id" mat-raised-button + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.validate' | translate }}" + [disabled]="emptyDocument" + (click)="onDocumentValidateButtonClicked()"> + <mat-icon>check_circle</mat-icon> + <span>{{ "document.edit.panel.button.validate" | translate }}</span> + </button> + <button id="GenerateResource_id" + *ngIf="isNotReviewMode" + mat-raised-button + color="primary" + [disabled]="!documentEditable" + matTooltip="{{ 'document.edit.panel.tooltip.generate' | translate }}" + (click)="onGenerateButtonClicked()"> + <mat-icon>change_circle</mat-icon> + <span>{{ "document.edit.panel.button.generate" | translate }}</span> + </button> + <button id="documentWizard_id" mat-raised-button + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.document.wizard' | translate }}" + [disabled]="!documentEditable" + *ngIf="showWizardDialog && isNotReviewMode" + (click)="onShowDocumentWizardDialog()"> + <mat-icon>code_block</mat-icon> + <span>{{ "document.edit.panel.button.document.wizard" | translate }}</span> + </button> + <span style="flex: 1 1 auto;"></span> + </mat-toolbar-row> + </mat-toolbar> + + <div class="panel" [formGroup]="documentForm" + style="display: flex;flex-direction: row;flex-grow: 1; "> + <div + style="display:flex; overflow: auto;flex: 2;align-self: stretch; flex-direction: column;"> + + <div style="display: flex;flex-direction: row"> + <mat-form-field style="min-width: 140px" + subscriptSizing="dynamic" + appearance="fill" + > + <mat-label>{{ "document.edit.panel.label.selected.version" | translate }}</mat-label> + <mat-select + + placeholder="{{ 'document.edit.panel.placeholder.version' | translate }}" + matTooltip="{{ 'document.edit.panel.tooltip.version' | translate }}" + id="document-version_id" + formControlName="payloadVersion" + (selectionChange)="onSelectionDocumentVersionChanged()"> + <mat-option *ngFor="let version of getDocumentVersions" + [value]="version"> + {{ version }} + </mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field style="min-width: 180px" + subscriptSizing="dynamic" + appearance="fill"> + <mat-label>{{ "document.edit.panel.label.selected.status" | translate }}</mat-label> + <input matInput id="status_id" + formControlName="documentVersionStatus" + readonly> + </mat-form-field> + <mat-form-field style="width:100%" + subscriptSizing="dynamic" + appearance="fill"> + <mat-label>{{ "document.edit.panel.label.selected.created.on" | translate }}</mat-label> + <input id="payloadCreatedOn_id" + matInput [ngxMatDatetimePicker]="payloadCreatedOnPicker" + formControlName="payloadCreatedOn" + readonly> + <mat-datepicker-toggle matSuffix [for]="payloadCreatedOnPicker" + style="visibility: hidden"></mat-datepicker-toggle> + <ngx-mat-datetime-picker #payloadCreatedOnPicker [showSpinners]="true" + [showSeconds]="false" + [hideTime]="false"></ngx-mat-datetime-picker> + </mat-form-field> + </div> + <div + style="display:block; overflow: auto;flex: 2;align-self: stretch; flex-direction: column;border: ridge 3px #b0bec5" + (click)="onEditPanelClick()"> + <smp-editor #smpDocumentEditor formControlName="payload" + ngDefaultControl></smp-editor> + + </div> + <smp-warning-panel *ngIf="!documentEditable" + icon="info" + type="desc" + [label]="'document.edit.panel.note.editable' | translate: { editableDocStatusList: editableDocStatusList }"> + </smp-warning-panel> + </div> + <expandable-panel> + <expandable-item icon="settings" + title="{{'document.properties.panel.tab.title' | translate }}" + buttonLabel="{{'document.properties.panel.tab.button.properties' | translate }}"> + <document-properties-panel formControlName="properties" + [showEditToolbarButton]="isNotReviewMode" + ngDefaultControl></document-properties-panel> + </expandable-item> + <expandable-item icon="list" + *ngIf="isNotReviewMode" + title="{{'document.versions.panel.tab.title' | translate }}" + buttonLabel="{{'document.versions.panel.tab.button.versions' | translate }}"> + <document-versions-panel + formControlName="documentVersions" + [selectedVersion]="currentDocumentVersion" + (selectedVersionChange) = "loadDocumentForVersion($event)" + ngDefaultControl></document-versions-panel> + </expandable-item> + <expandable-item icon="event" + title="{{'document.events.panel.tab.title' | translate }}" + buttonLabel="{{'document.events.panel.tab.button.events' | translate }}"> + <document-events-panel formControlName="documentVersionEvents" + ngDefaultControl></document-events-panel> + </expandable-item> + </expandable-panel> + </div> + + <mat-toolbar class="mat-elevation-z2" + style="flex-grow: 0;"> + <mat-toolbar-row class="smp-toolbar-row"> + <button id="back_id" mat-raised-button color="primary" + (click)="onBackButtonClicked()"> + <mat-icon>arrow_circle_left</mat-icon> + <span>{{ "document.edit.panel.button.back" | translate }}</span> + </button> + <button id="cancel_id" + *ngIf="isNotReviewMode" + mat-raised-button + color="primary" + [disabled]="cancelButtonDisabled" + (click)="onDocumentResetButtonClicked()"> + <mat-icon>cancel</mat-icon> + <span>{{ "document.edit.panel.button.cancel" | translate }}</span> + </button> + <tool-button-spacer></tool-button-spacer> + <button id="saveResource_id" + *ngIf="isNotReviewMode" + mat-raised-button + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.save' | translate }}" + [disabled]="saveButtonDisabled" + (click)="onSaveButtonClicked()"> + <mat-icon>save</mat-icon> + <span>{{ "document.edit.panel.button.save" | translate }}</span> + </button> + <button id="publishResource_id" mat-raised-button + *ngIf="isNotReviewMode" + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.version.publish' | translate }}" + [disabled]="publishButtonDisabled" + (click)="onPublishButtonClicked()"> + <mat-icon>publish</mat-icon> + <span>{{ "document.edit.panel.button.version.publish" | translate }}</span> + </button> + <tool-button-spacer></tool-button-spacer> + <button *ngIf="reviewEnabled && isNotReviewMode" id="reviewResource_id" mat-raised-button + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.version.review' | translate }}" + [disabled]="reviewButtonDisabled" + (click)="onReviewRequestButtonClicked()"> + <mat-icon>task</mat-icon> + <span>{{ "document.edit.panel.button.version.review" | translate }}</span> + </button> + <button mat-raised-button + *ngIf="!isNotReviewMode || reviewEnabled" + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.version.approve' | translate }}" + [disabled]="reviewActionButtonDisabled" + (click)="onApproveButtonClicked()"> + <mat-icon>check_circle</mat-icon> + <span>{{ "document.edit.panel.button.version.approve" | translate }}</span> + </button> + <button mat-raised-button + *ngIf="!isNotReviewMode || reviewEnabled" + color="primary" + matTooltip="{{ 'document.edit.panel.tooltip.version.reject' | translate }}" + [disabled]="reviewActionButtonDisabled" + (click)="onRejectButtonClicked()"> + <mat-icon>unpublished</mat-icon> + <span>{{ "document.edit.panel.button.version.reject" | translate }}</span> + </button> + </mat-toolbar-row> + </mat-toolbar> +</div> diff --git a/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.scss b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..629dfc616c3572d572df60f2224542a1870ebfa5 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.scss @@ -0,0 +1,10 @@ +#resource-document-panel { + display: flex; + height: 100%; + flex-direction: column; + +} + +.CodeMirror { + height: auto; +} diff --git a/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.ts b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb676f82bd2ab500e8a2ee832891af5babb209b6 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-edit-panel/document-edit-panel.component.ts @@ -0,0 +1,619 @@ +import { + Component, + Input, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import { + BeforeLeaveGuard +} from "../../../window/sidenav/navigation-on-leave-guard"; +import {GroupRo} from "../../../common/model/group-ro.model"; +import {ResourceRo} from "../../../common/model/resource-ro.model"; +import { + AlertMessageService +} from "../../../common/alert-message/alert-message.service"; +import {DomainRo} from "../../../common/model/domain-ro.model"; +import { + ResourceDefinitionRo +} from "../../../system-settings/admin-extension/resource-definition-ro.model"; +import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; +import {DocumentRo} from "../../../common/model/document-ro.model"; +import { + NavigationService +} from "../../../window/sidenav/navigation-model.service"; + +import { + ConfirmationDialogComponent +} from "../../../common/dialogs/confirmation-dialog/confirmation-dialog.component"; +import { + SmpEditorComponent +} from "../../../common/components/smp-editor/smp-editor.component"; +import {EntityStatus} from "../../../common/enums/entity-status.enum"; +import {TranslateService} from "@ngx-translate/core"; +import {lastValueFrom, Observer} from "rxjs"; +import { + DocumentVersionsStatus +} from "../../../common/enums/document-versions-status.enum"; +import { + HttpErrorHandlerService +} from "../../../common/error/http-error-handler.service"; +import { + DocumentWizardDialogComponent +} from "../../../edit/edit-resources/document-wizard-dialog/document-wizard-dialog.component"; +import { + EditResourceService +} from "../../../edit/edit-resources/edit-resource.service"; +import {SubresourceRo} from "../../model/subresource-ro.model"; +import { + SubresourceWizardRo +} from "../../../edit/edit-resources/subresource-document-wizard-dialog/subresource-wizard-edit-ro.model"; +import { + SubresourceDocumentWizardComponent +} from "../../../edit/edit-resources/subresource-document-wizard-dialog/subresource-document-wizard.component"; +import { + ReviewDocumentVersionRo +} from "../../model/review-document-version-ro.model"; + +export enum SmpDocumentEditorType { + RESOURCE_EDITOR = "RESOURCE_EDITOR", + SUBRESOURCE_EDITOR = "SUBRESOURCE_EDITOR", + REVIEW_EDITOR = "REVIEW_EDITOR" +} + +export enum SmpReviewDocumentTarget { + RESOURCE = "RESOURCE", + SUBRESOURCE = "SUBRESOURCE", +} + +/** + * Component edit panel for document and document version management. + * Please not the document version management can change the document (version) entities only + * and does not edit resource or subresource entities. + * + * The document (versions) actions can be + * <ul> + * <li>new version created: a new version of the document can be created for the document</li> + * <li>edited: a document version payload, or document version can be loaded </li> + * <li>saved: a document changed can be persisted</li> + * <li>published: the document can be set as current version</li> + * <li>validated: the selected document can be validated </li> + * <li>generated: the button allows option to generate new document from the plugin template</li> + * <li>reviewed process: the component allows action needed for the review process + * <ul> + * <li>request review</li> + * <li>approve</li> + * <li>reject</li> + * </ul> + * </li> + * </ul> + */ +@Component({ + selector: 'document-edit-panel', + templateUrl: './document-edit-panel.component.html', + styleUrls: ['./document-edit-panel.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class DocumentEditPanelComponent implements BeforeLeaveGuard, OnInit { + readonly reviewAllowedStatusList: DocumentVersionsStatus[] = [DocumentVersionsStatus.DRAFT, DocumentVersionsStatus.REJECTED, DocumentVersionsStatus.RETIRED]; + readonly editableDocStatusList: DocumentVersionsStatus[] = [DocumentVersionsStatus.DRAFT, DocumentVersionsStatus.REJECTED, DocumentVersionsStatus.RETIRED]; + readonly publishableDocStatusList: DocumentVersionsStatus[] = [DocumentVersionsStatus.DRAFT, DocumentVersionsStatus.APPROVED, DocumentVersionsStatus.RETIRED]; + private resource: ResourceRo; + private subresource: SubresourceRo; + private reviewDocument: ReviewDocumentVersionRo; + private isResourceDocument:boolean = true; + + _document: DocumentRo; + @Input() private group: GroupRo; + @Input() domain: DomainRo; + @Input() domainResourceDefs: ResourceDefinitionRo[]; + private _editorMode: SmpDocumentEditorType = SmpDocumentEditorType.RESOURCE_EDITOR; + + @Input() set editorMode(edType: string) { + this._editorMode = SmpDocumentEditorType[edType]; + } + + get editorMode(): SmpDocumentEditorType { + return !this._editorMode ? SmpDocumentEditorType.RESOURCE_EDITOR : this._editorMode; + } + + @ViewChild("smpDocumentEditor") documentEditor: SmpEditorComponent; + // ---- + // defined observers + loadDocumentObserver: Partial<Observer<DocumentRo>> = { + next: async (doc: DocumentRo) => { + if (!doc) { + this.document = null; + } else { + this.document = doc; + } + }, + error: (err: any) => { + this.httpErrorHandlerService.handleHttpError(err) + } + }; + + reviewActionDocumentObserver: Partial<Observer<DocumentRo>> = { + next: async (doc: DocumentRo) => { + if (!doc) { + this.document = null; + } else { + this.document = doc; + } + }, + error: (err: any) => { + this.httpErrorHandlerService.handleHttpError(err) + } + }; + + // save event observer + saveDocumentObserver: Partial<Observer<DocumentRo>> = { + next: async (doc: DocumentRo) => { + if (!doc) { + this.document = null; + } else { + this.alertService.success(await lastValueFrom(this.translateService.get("document.edit.panel.success.save", {currentResourceVersion: doc.payloadVersion}))); + this.document = doc; + } + }, + error: (err: any) => { + this.httpErrorHandlerService.handleHttpError(err) + } + }; + + // save event observer + validateDocumentObserver: Partial<Observer<DocumentRo>> = { + next: async (doc: DocumentRo) => { + this.alertService.success(await lastValueFrom(this.translateService.get("document.edit.panel.success.valid"))) + }, + error: (err: any) => { + this.httpErrorHandlerService.handleHttpError(err) + } + }; + + // save event observer + generateDocumentObserver: Partial<Observer<DocumentRo>> = { + next: async (doc: DocumentRo) => { + if (!doc) { + this.document = null; + } else { + this.alertService.success(await lastValueFrom(this.translateService.get("document.edit.panel.success.generate"))) + this.documentForm.controls['payload'].setValue(doc.payload); + this.documentForm.controls['payload'].markAsDirty(); + } + }, + error: (err: any) => { + this.httpErrorHandlerService.handleHttpError(err) + } + }; + + documentForm: FormGroup; + + constructor(private editResourceService: EditResourceService, + private alertService: AlertMessageService, + private dialog: MatDialog, + private navigationService: NavigationService, + private formBuilder: FormBuilder, + private translateService: TranslateService, + private httpErrorHandlerService: HttpErrorHandlerService) { + + + this.documentForm = formBuilder.group({ + 'mimeType': new FormControl({value: null}), + 'name': new FormControl({value: null}), + 'currentResourceVersion': new FormControl({value: null}), + 'payloadCreatedOn': new FormControl({value: null}), + 'payloadVersion': new FormControl({value: null}), + 'payload': new FormControl({value: null}), + 'properties': new FormControl({value: null}), + 'documentVersionStatus': new FormControl({value: null}), + 'documentVersionEvents': new FormControl({value: null}), + 'documentVersions': new FormControl({value: null}), + }); + + this.resource = editResourceService.selectedResource; + this.subresource = editResourceService.selectedSubresource; + this.reviewDocument = editResourceService.selectedReviewDocument; + this.documentForm.controls['payload'].setValue("") + } + + /** + * Methods created resource and subresource from documentReview object + */ + initFromDocumentReview() { + if (!this.reviewDocument) { + return + } + this.resource = { + resourceId: this.reviewDocument.resourceId, + identifierScheme: this.reviewDocument.resourceIdentifierScheme, + identifierValue: this.reviewDocument.resourceIdentifierValue, + reviewEnabled: true, + visibility: null, + resourceTypeIdentifier: null, + } as ResourceRo; + + if (this.reviewDocument.subresourceId) { + this.subresource = { + subresourceId: this.reviewDocument.subresourceId, + identifierScheme: this.reviewDocument.subresourceIdentifierScheme, + identifierValue: this.reviewDocument.subresourceIdentifierValue, + subresourceTypeIdentifier: null, + } as SubresourceRo; + } + if (this.reviewDocument.target === "RESOURCE") { + this.isResourceDocument = true; + } else { + this.isResourceDocument = false; + } + + + } + + + ngOnInit(): void { + if (this.editorMode === SmpDocumentEditorType.REVIEW_EDITOR) { + this.initFromDocumentReview(); + } + + if (this.editorMode === SmpDocumentEditorType.REVIEW_EDITOR && !this.reviewDocument + || this.editorMode !== SmpDocumentEditorType.REVIEW_EDITOR && !this.resource) { + this.alertService.errorForTranslation("document.edit.panel.error.document.null"); + this.navigationService.navigateUp(); + return; + } + if (this.editorMode === SmpDocumentEditorType.REVIEW_EDITOR) { + this.loadDocumentForVersion(this.reviewDocument.version); + } else { + this.loadDocumentForVersion(); + } + } + + + @Input() set document(value: DocumentRo) { + this._document = value; + this.documentForm.disable(); + console.log("Document with properties: " + value?.properties) + if (!!value) { + this.documentEditor.mimeType = value.mimeType; + this.documentForm.controls['mimeType'].setValue(value.mimeType); + this.documentForm.controls['name'].setValue(value.name); + this.documentForm.controls['currentResourceVersion'].setValue(value.currentResourceVersion); + this.documentForm.controls['payloadVersion'].setValue(value.payloadVersion); + this.documentForm.controls['payloadCreatedOn'].setValue(value.payloadCreatedOn); + this.documentForm.controls['payload'].setValue(value.payload); + this.documentForm.controls['properties'].setValue(value.properties); + this.documentForm.controls['documentVersionStatus'].setValue(value.documentVersionStatus); + this.documentForm.controls['documentVersionEvents'].setValue(value.documentVersionEvents); + this.documentForm.controls['documentVersions'].setValue(value.documentVersions); + // the method documentVersionsExists already uses the current value to check if versions exists + if (this.documentVersionsExists && this.isNotReviewMode) { + this.documentForm.controls['payloadVersion'].enable(); + } + if (this.documentEditable) { + this.documentForm.controls['payload'].enable(); + } + } else { + this.documentForm.controls['name'].setValue(""); + this.documentForm.controls['payload'].setValue(""); + this.documentForm.controls['currentResourceVersion'].setValue(""); + this.documentForm.controls['payloadVersion'].setValue(""); + this.documentForm.controls['payloadCreatedOn'].setValue(""); + this.documentForm.controls['payload'].setValue(""); + this.documentForm.controls['properties'].setValue([]); + this.documentForm.controls['documentVersionStatus'].setValue(""); + this.documentForm.controls['documentVersionEvents'].setValue([]); + this.documentForm.controls['documentVersions'].setValue([]); + } + this.documentForm.markAsPristine(); + } + + get document(): DocumentRo { + let doc: DocumentRo = {...this._document}; + if (this.documentForm.controls['payload'].dirty) { + doc.payload = this.documentForm.controls['payload'].value; + doc.payloadStatus = EntityStatus.UPDATED; + } + doc.properties = this.documentForm.controls['properties'].value; + return doc; + } + + onBackButtonClicked(): void { + this.navigationService.navigateUp(); + } + + async onDocumentResetButtonClicked() { + this.dialog.open(ConfirmationDialogComponent, { + data: { + title: await lastValueFrom(this.translateService.get("document.edit.panel.cancel.confirmation.dialog.title")), + description: await lastValueFrom(this.translateService.get("document.edit.panel.cancel.confirmation.dialog.description")) + } + }).afterClosed().subscribe(result => { + if (result) { + this.resetChanges() + } + }); + } + + resetChanges() { + let currentVersion = this._document?.payloadVersion; + if (!currentVersion) { + this.documentForm.controls['payload'].setValue(""); + this.documentForm.markAsPristine(); + } else { + this.loadDocumentForVersion(currentVersion); + } + } + + get currentDocumentVersion(): number { + return this.isNotReviewMode? this.documentForm.controls['payloadVersion']?.value: + this.reviewDocument?.version; + } + + onSaveButtonClicked(): void { + let onSaveObservable = this.isResourceDocument ? + this.editResourceService.saveResourceDocumentObservable(this.resource, this.document) : + this.editResourceService.saveSubresourceDocumentObservable(this.subresource, this.resource, this.document); + onSaveObservable.subscribe(this.saveDocumentObserver); + } + + onReviewRequestButtonClicked(): void { + // create lightweight document object + let docRequest: DocumentRo = { + documentId: this._document.documentId, + payloadVersion: this._document.payloadVersion, + } as DocumentRo; + + let onReviewRequestObservable = this.isResourceDocument ? + this.editResourceService.reviewRequestForResourceDocumentObservable(this.resource, docRequest) : + this.editResourceService.reviewRequestForSubresourceDocumentObservable(this.subresource, this.resource, docRequest); + // request review + onReviewRequestObservable.subscribe(this.loadDocumentObserver); + } + + onApproveButtonClicked(): void { + // create lightweight document object + let docRequest: DocumentRo = { + documentId: this._document.documentId, + payloadVersion: this._document.payloadVersion, + } as DocumentRo; + + let onReviewRequestObservable = this.isResourceDocument ? + this.editResourceService.reviewApproveForResourceDocumentObservable(this.resource, docRequest) : + this.editResourceService.reviewApproveForSubresourceDocumentObservable(this.subresource, this.resource, docRequest); + // request review + onReviewRequestObservable.subscribe(this.reviewActionDocumentObserver); + } + + onRejectButtonClicked(): void { + // create lightweight document object + let docRequest: DocumentRo = { + documentId: this._document.documentId, + payloadVersion: this._document.payloadVersion, + } as DocumentRo; + + let onReviewRequestObservable = this.isResourceDocument ? + this.editResourceService.reviewRejectResourceDocumentObservable(this.resource, docRequest) : + this.editResourceService.reviewRejectSubresourceDocumentObservable(this.subresource, this.resource, docRequest); + // request review + onReviewRequestObservable.subscribe(this.reviewActionDocumentObserver); + } + + /** + * Publish the current document version + * + */ + onPublishButtonClicked(): void { + // create lightweight document object + let docRequest: DocumentRo = { + documentId: this._document.documentId, + payloadVersion: this._document.payloadVersion, + } as DocumentRo; + + let onReviewRequestObservable = this.isResourceDocument ? + this.editResourceService.publishResourceDocumentObservable(this.resource, docRequest) : + this.editResourceService.publishSubresourceDocumentObservable(this.subresource, this.resource, docRequest); + // request review + onReviewRequestObservable.subscribe(this.loadDocumentObserver); + } + + onGenerateButtonClicked(): void { + let generateObservable = this.isResourceDocument ? + this.editResourceService.generateResourceDocumentObservable(this.resource) : + this.editResourceService.generateSubresourceDocumentObservable(this.subresource, this.resource); + generateObservable.subscribe(this.generateDocumentObserver); + } + + async onShowDocumentWizardDialog() { + if (this.isResourceDocument) { + await this.onShowResourceDocumentWizardDialog(); + } else { + await this.onShowSubresourceDocumentWizardDialog(); + } + } + + async onShowResourceDocumentWizardDialog() { + const formRef: MatDialogRef<any> = this.dialog.open(DocumentWizardDialogComponent, { + data: { + title: await lastValueFrom(this.translateService.get("document.edit.panel.document.wizard.dialog.title")), + resource: this.resource, + + } + }); + formRef.afterClosed().subscribe(result => { + if (result) { + let val = formRef.componentInstance.getExtensionXML(); + this.documentForm.controls['payload'].setValue(val); + this.documentForm.controls['payload'].markAsDirty(); + } + }); + } + + async onShowSubresourceDocumentWizardDialog() { + + let serviceMetadataWizard: SubresourceWizardRo = { + isNewSubresource: false, + participantIdentifier: this.resource.identifierValue, + participantScheme: this.resource.identifierScheme, + documentIdentifier: this.subresource.identifierValue, + documentIdentifierScheme: this.subresource.identifierScheme, + processIdentifier: '', + processScheme: '', + transportProfile: 'bdxr-transport-ebms3-as4-v1p0', // default value for oasis AS4 + + endpointUrl: '', + endpointCertificate: '', + + serviceDescription: '', + technicalContactUrl: '', + + } + + const formRef: MatDialogRef<any> = this.dialog.open(SubresourceDocumentWizardComponent, { + data: serviceMetadataWizard + }); + formRef.afterClosed().subscribe(result => { + if (result) { + let smw: SubresourceWizardRo = formRef.componentInstance.getCurrent(); + this.documentForm.controls['payload'].setValue(smw.contentXML); + this.documentForm.controls['payload'].markAsDirty(); + } + }); + } + + /** + * 'loadDocumentForVersion' load the document for the given version + * @param version + */ + loadDocumentForVersion(version: number = null): void { + + let loadObservable = this.isResourceDocument ? + this.editResourceService.getResourceDocumentObservable(this.resource, version) : + this.editResourceService.getSubresourceDocumentObservable(this.subresource, this.resource, version); + loadObservable.subscribe(this.loadDocumentObserver); + } + + /** + * Submit the current document for validation to the server + */ + validateCurrentDocument(): void { + let validateObservable = this.isResourceDocument ? + this.editResourceService.validateResourceDocumentObservable(this.resource, this.document) : + this.editResourceService.validateSubresourceDocumentObservable(this.subresource, this.resource, this.document); + validateObservable.subscribe(this.validateDocumentObserver); + } + + + onDocumentValidateButtonClicked(): void { + this.validateCurrentDocument(); + } + + onNewDocumentVersionButtonClicked(): void { + // create a new version of the document + let docRequest: DocumentRo = { + documentVersionStatus: DocumentVersionsStatus.DRAFT, + payloadStatus: EntityStatus.NEW, + status: EntityStatus.UPDATED, + payload: this.documentForm.controls['payload'].value, + allVersions: this.getDocumentVersions, + // set from current document + documentVersions: this.documentForm.controls['documentVersions'].value, + properties: this.documentForm.controls['properties'].value, + } as DocumentRo; + // set as current + this.document = docRequest; + this.documentForm.markAsDirty(); + } + + onSelectionDocumentVersionChanged(): void { + this.loadDocumentForVersion(this.documentForm.controls['payloadVersion'].value) + } + + public onEditPanelClick() { + + if (this.documentEditor.hasFocus) { + return; + } + this.documentEditor.focusAndCursorToEnd(); + } + + get getDocumentVersions(): number[] { + return !this._document?.allVersions ? [] : this._document?.allVersions; + } + + get emptyDocument(): boolean { + return !this.documentForm.controls['payload']?.value + } + + get documentVersionsExists(): boolean { + return this.getDocumentVersions.length > 0 + } + + get cancelButtonDisabled(): boolean { + return !this.documentForm.dirty; + } + + get saveButtonDisabled(): boolean { + return !this.documentForm.dirty + || !this.documentForm.controls['payload']?.value; + } + + /** + * Review button is disabled if review is not enabled, + * if changes are not persisted, or not in status, rejected, retired or draft + */ + get reviewButtonDisabled(): boolean { + return !this.reviewEnabled + || !this.documentSubmitReviewAllowed + || this.isDirty(); + } + + get reviewActionButtonDisabled(): boolean { + return !this.reviewEnabled + || this.documentForm.controls['documentVersionStatus']?.value !== DocumentVersionsStatus.UNDER_REVIEW + } + + get publishButtonDisabled(): boolean { + // can not publish changed document + if (this.isDirty()) { + return true; + } + let status = this.documentForm.controls['documentVersionStatus']?.value + + return this.reviewEnabled ? + status !== DocumentVersionsStatus.APPROVED : + !this.publishableDocStatusList.find(i => i === status) + } + + get documentEditable(): boolean { + let status = this.documentForm.controls['documentVersionStatus']?.value + return !!this.editableDocStatusList.find(i => i === status) + } + + get documentSubmitReviewAllowed(): boolean { + let status = this.documentForm.controls['documentVersionStatus']?.value + return !!this.reviewAllowedStatusList.find(i => i === status) + } + + get reviewEnabled(): boolean { + return this.resource?.reviewEnabled; + } + + get isNotReviewMode(): boolean { + return this.editorMode !== SmpDocumentEditorType.REVIEW_EDITOR; + } + + isDirty(): boolean { + return this.documentForm.dirty + } + + get showWizardDialog(): boolean { + if (this.isResourceDocument) { + // in version DomiSMP 5.0 CR show only the wizard for edelivery-oasis-smp-1.0-servicegroup + return this.resource?.resourceTypeIdentifier === 'edelivery-oasis-smp-1.0-servicegroup'; + } else { + return this.subresource?.subresourceTypeIdentifier === 'edelivery-oasis-smp-1.0-servicemetadata'; + } + } +} diff --git a/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.html b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..595803b18bee6c38ec1ab5f23f31667a9cfcbfc9 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.html @@ -0,0 +1,58 @@ +<div style="display:flex; flex-grow: 1; flex-direction: column"> + <mat-form-field style="width: 100%"> + <mat-label>{{ "document.events.panel.label.filter" | translate }}</mat-label> + <input matInput (keyup)="applyFilter($event)" #input> + </mat-form-field> + <div id="events-table-container"> + <table #DocumentEventTable mat-table class="mat-elevation-z1" + style="max-height: 100%;flex-grow: 1;" + id="document-events-table" + [dataSource]="eventDataSource" matSort> + + <ng-container matColumnDef="date"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.events.panel.label.date" | translate }} + </th> + <td mat-cell *matCellDef="let row;"> + {{ row.eventOn | date: dateTimeFormat }} + </td> + </ng-container> + <ng-container matColumnDef="eventType"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.events.panel.label.type" | translate }} + </th> + <td mat-cell *matCellDef="let row">{{ row.eventType }}</td> + </ng-container> + + <ng-container matColumnDef="username"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.events.panel.label.username" | translate }} + </th> + <td mat-cell *matCellDef="let row">{{ row.username }}</td> + </ng-container> + <ng-container matColumnDef="eventSource"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.events.panel.label.source" | translate }} + </th> + <td mat-cell *matCellDef="let row">{{ row.eventSourceType }}</td> + </ng-container> + + <tr mat-header-row + *matHeaderRowDef="displayedColumns; sticky:true"></tr> + <tr mat-row + *matRowDef="let odd = odd; let row; columns: displayedColumns;" + [ngClass]="getRowClass(row, odd)" + ></tr> + <tr class="mat-row" *matNoDataRow> + <td class="mat-cell" + colspan="2">{{ "document.events.panel.label.no.properties.found" | translate }} + </td> + </tr> + </table> + </div> + <mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons + [pageSize]="10" [length]="eventDataSource.data?.length" + attr.aria-label="{{ 'document.events.panel.label.select.page' | translate }}"></mat-paginator> + +</div> + diff --git a/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.scss b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..ba3e29071a33c3c07c2de2cf915be8d46c226833 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.scss @@ -0,0 +1,20 @@ +#document-events-panel { + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 0 1em; + min-width: 600px; +} + +#events-table-container { + position: relative; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-y: scroll; +} + +#events-table-container table { + width: 100%; +} diff --git a/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.ts b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c22cc2a6298b302fc024799744238fe8e47348ee --- /dev/null +++ b/smp-angular/src/app/common/panels/document-events-panel/document-events-panel.component.ts @@ -0,0 +1,141 @@ +/*- + * #START_LICENSE# + * smp-webapp + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +import { + AfterViewInit, + Component, + forwardRef, + Input, + ViewChild, +} from '@angular/core'; +import {MatTable, MatTableDataSource} from "@angular/material/table"; +import { + ControlContainer, + ControlValueAccessor, + FormControl, + FormControlDirective, + NG_VALUE_ACCESSOR +} from "@angular/forms"; +import {MatSort} from "@angular/material/sort"; +import {MatDialog} from "@angular/material/dialog"; +import {MatPaginator} from "@angular/material/paginator"; +import {DocumentVersionEventRo} from "../../model/document-version-event-ro.model"; +import { + BeforeLeaveGuard +} from "../../../window/sidenav/navigation-on-leave-guard"; +import {GlobalLookups} from "../../global-lookups"; + +/** + * Component to display the properties of a document in a table. The properties can be edited and saved. + * @author Joze Rihtarsic + * @since 5.1 + */ +@Component({ + selector: 'document-events-panel', + templateUrl: './document-events-panel.component.html', + styleUrls: ['./document-events-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DocumentEventsPanelComponent), + multi: true + } + ] +}) +export class DocumentEventsPanelComponent implements AfterViewInit, BeforeLeaveGuard, ControlValueAccessor { + + + displayedColumns: string[] = [ 'date', 'eventType', 'username', 'eventSource']; + private onChangeCallback: (_: any) => void = () => { + }; + eventDataSource: MatTableDataSource<DocumentVersionEventRo> = new MatTableDataSource(); + dataChanged: boolean = false; + selected: DocumentVersionEventRo; + @ViewChild("DocumentVersionEventTable") table: MatTable<DocumentVersionEventRo>; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + constructor( + private globalLookups: GlobalLookups, + public dialog: MatDialog, + private controlContainer: ControlContainer) { + } + + get dateTimeFormat(): string { + return this.globalLookups.getDateTimeFormat(); + } + + @ViewChild(FormControlDirective, {static: true}) + formControlDirective: FormControlDirective; + @Input() + formControl: FormControl; + + @Input() + formControlName: string; /* get hold of FormControl instance no matter formControl or formControlName is given. If formControlName is given, then this.controlContainer.control is the parent FormGroup (or FormArray) instance. */ + get control() { + return this.formControl || this.controlContainer.control.get(this.formControlName); + } + + /** + * Implementation of the ControlValueAccessor method to write value to the component. + * @param eventList + */ + writeValue(eventList: DocumentVersionEventRo[]): void { + this.eventDataSource.data = !eventList?.length ? [] : [...eventList]; + this.dataChanged = false; + } + + ngAfterViewInit() { + this.eventDataSource.paginator = this.paginator; + this.eventDataSource.sort = this.sort; + } + + applyFilter(event: Event) { + const filterValue: string = (event.target as HTMLInputElement).value; + this.eventDataSource.filter = filterValue?.trim().toLowerCase(); + + if (this.eventDataSource.paginator) { + this.eventDataSource.paginator.firstPage(); + } + } + + isDirty(): boolean { + return this.dataChanged; + } + + registerOnChange(fn: any): void { + this.onChangeCallback = fn; + + } + + registerOnTouched(fn: any): void { + // not implemented + } + + setDisabledState(isDisabled: boolean): void { + // not implemented + } + + getRowClass(row, oddRow: boolean) { + return { + 'datatable-row-selected': row === this.selected, + 'datatable-row-odd': oddRow + }; + } + +} diff --git a/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.html b/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.html index b6047e8f2d9efdd4879c4802851bed64efff53f7..f1dc9a7eab8c8f754220293913435c77df8fbe89 100644 --- a/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.html +++ b/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.html @@ -1,6 +1,36 @@ -<div style="display: flex;flex-direction: row;height: 100%"> - <div *ngIf="showPropertyPanel" id="document-properties-panel"> - <div style="display:flex; flex-grow: 1; flex-direction: column"> +<div style="display:flex; flex-grow: 1; flex-direction: column"> + <mat-toolbar *ngIf="shotToolbar" + class="mat-elevation-z2" + style="flex-grow: 0;"> + <mat-toolbar-row class="smp-toolbar-row"> + <button id="createButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.create' | translate }}" + matTooltip="{{ 'document.properties.panel.tooltip.create' | translate }}" + (click)="onCreateProperty()"> + <mat-icon>add</mat-icon> + </button> + <button id="editButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.edit' | translate }}" + matTooltip="{{ 'document.properties.panel.tooltip.edit' | translate }}" + (click)="onEditSelectedProperty()" + [disabled]="editButtonDisabled"> + <mat-icon>edit</mat-icon> + </button> + <button id="deleteButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.delete.remove' | translate }}" + matTooltip="{{ 'document.properties.panel.tooltip.delete.remove' | translate }}" + (click)="onDeleteSelectedProperty()" + [disabled]="deleteButtonDisabled" + > + <mat-icon>remove</mat-icon> + </button> + <tool-button-spacer></tool-button-spacer> + <button id="resetButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.reset' | translate }}" + matTooltip="{{ 'document.properties.panel.tooltip.reset' | translate }}" + (click)="onResetButtonClicked()" + color="primary" + [disabled]="!cancelButtonEnabled"> + <mat-icon>refresh</mat-icon> + </button> + </mat-toolbar-row> + </mat-toolbar> <mat-form-field style="width: 100%"> <mat-label>{{ "document.properties.panel.label.filter" | translate }}</mat-label> <input matInput (keyup)="applyFilter($event)" #input> @@ -39,41 +69,3 @@ attr.aria-label="{{ 'document.properties.panel.label.select.page' | translate }}"></mat-paginator> </div> - </div> - <!-- table Toolbar --> - <div style="width: 42px"> - <button mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.expand.collapse' | translate }}" - matTooltip="{{ 'document.properties.panel.tooltip.expand.collapse' | translate }}" - (click)="onToggleButtonClicked()"> - <mat-icon *ngIf="showPropertyPanel">chevron_left</mat-icon> - <mat-icon *ngIf="!showPropertyPanel">chevron_right</mat-icon> - </button> - <tool-button-spacer [vertical]="false"></tool-button-spacer> - <button id="createButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.create' | translate }}" - matTooltip="{{ 'document.properties.panel.tooltip.create' | translate }}" - (click)="onCreateProperty()"> - <mat-icon>add</mat-icon> - </button> - <button id="editButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.edit' | translate }}" - matTooltip="{{ 'document.properties.panel.tooltip.edit' | translate }}" - (click)="onEditSelectedProperty()" - [disabled]="editButtonDisabled"> - <mat-icon>edit</mat-icon> - </button> - <button id="deleteButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.delete.remove' | translate }}" - matTooltip="{{ 'document.properties.panel.tooltip.delete.remove' | translate }}" - (click)="onDeleteSelectedProperty()" - [disabled]="deleteButtonDisabled" - > - <mat-icon>remove</mat-icon> - </button> - <tool-button-spacer [vertical]="false"></tool-button-spacer> - <button id="resetButton" mat-mini-fab attr.aria-label="{{ 'document.properties.panel.label.reset' | translate }}" - matTooltip="{{ 'document.properties.panel.tooltip.reset' | translate }}" - (click)="onResetButtonClicked()" - color="primary" - [disabled]="!cancelButtonEnabled"> - <mat-icon>refresh</mat-icon> - </button> - </div> -</div> diff --git a/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.ts b/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.ts index 3e067286b9a3ad539e3ebf02ba679208dea2c416..5c346cf7ce0b54b4fd422a03f3802c50d54c92b2 100644 --- a/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.ts +++ b/smp-angular/src/app/common/panels/document-properties-panel/document-properties-panel.component.ts @@ -68,13 +68,13 @@ export class DocumentPropertiesPanelComponent implements AfterViewInit, BeforeLe private onChangeCallback: (_: any) => void = () => { }; selected?: DocumentPropertyRo = null; - dataChanged: boolean = false - showPropertyPanel: boolean = true; + @Input() showEditToolbarButton: boolean = true; + dataChanged: boolean = false; initPropertyList: DocumentPropertyRo[] = []; propertyDataSource: MatTableDataSource<DocumentPropertyRo> = new MatTableDataSource(); @ViewChild("DocumentPropertyTable") table: MatTable<DocumentPropertyRo>; - @ViewChild(MatPaginator) paginator: MatPaginator + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; constructor(public dialog: MatDialog, private controlContainer: ControlContainer) { @@ -115,8 +115,8 @@ export class DocumentPropertiesPanelComponent implements AfterViewInit, BeforeLe return this.dataChanged; } - public onToggleButtonClicked(): void { - this.showPropertyPanel = !this.showPropertyPanel; + get shotToolbar(): boolean { + return this.showEditToolbarButton; } /** diff --git a/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.html b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..888070643239c2374d704d1dae8e81343e0a52b0 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.html @@ -0,0 +1,59 @@ +<div style="display:flex; flex-grow: 1; flex-direction: column"> + <mat-form-field style="width: 100%"> + <mat-label>{{ "document.versions.panel.label.filter" | translate }}</mat-label> + <input matInput (keyup)="applyFilter($event)" #input> + </mat-form-field> + <div id="versions-table-container"> + <table #DocumentVersionsTable mat-table class="mat-elevation-z1" + style="max-height: 100%;flex-grow: 1;" + id="document-versions-table" + [dataSource]="versionDataSource" matSort> + + <ng-container matColumnDef="version"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.versions.panel.label.version" | translate }} + </th> + <td mat-cell *matCellDef="let row">{{ row.version }}</td> + </ng-container> + <ng-container matColumnDef="status"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.versions.panel.label.status" | translate }} + </th> + <td mat-cell *matCellDef="let row">{{ row.versionStatus }}</td> + </ng-container> + <ng-container matColumnDef="createdOn"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.versions.panel.label.created" | translate }} + </th> + <td mat-cell *matCellDef="let row;"> + {{ row.createdOn | date: dateTimeFormat }} + </ng-container> + + <ng-container matColumnDef="lastUpdatedOn"> + <th mat-header-cell *matHeaderCellDef + mat-sort-header>{{ "document.versions.panel.label.updated" | translate }} + </th> + <td mat-cell *matCellDef="let row;"> + {{ row.lastUpdatedOn | date: dateTimeFormat }} + </ng-container> + + <tr mat-header-row + *matHeaderRowDef="displayedColumns; sticky:true"></tr> + <tr mat-row + *matRowDef="let odd = odd; let row; columns: displayedColumns;" + (dblclick)="onRowSelect(row)" + [ngClass]="getRowClass(row, odd)" + ></tr> + <tr class="mat-row" *matNoDataRow> + <td class="mat-cell" + colspan="2">{{ "document.versions.panel.label.no.properties.found" | translate }} + </td> + </tr> + </table> + </div> + <mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons + [pageSize]="10" [length]="versionDataSource.data?.length" + attr.aria-label="{{ 'document.versions.panel.label.select.page' | translate }}"></mat-paginator> + +</div> + diff --git a/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.scss b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..8d66e8a15434e1df7016833fcfe5d3ec7e87a935 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.scss @@ -0,0 +1,20 @@ +#document-version-panel { + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 0 1em; + min-width: 600px; +} + +#version-table-container { + position: relative; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-y: scroll; +} + +#version-table-container table { + width: 100%; +} diff --git a/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.ts b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..559fcba50d9c5d7ff6169b9d23b9e900f4c5aec4 --- /dev/null +++ b/smp-angular/src/app/common/panels/document-versions-panel/document-versions-panel.component.ts @@ -0,0 +1,178 @@ +/*- + * #START_LICENSE# + * smp-webapp + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +import { + AfterViewInit, + Component, + EventEmitter, + forwardRef, + Input, + Output, + ViewChild, +} from '@angular/core'; +import {MatTable, MatTableDataSource} from "@angular/material/table"; +import { + ControlContainer, + ControlValueAccessor, + FormControl, + FormControlDirective, + NG_VALUE_ACCESSOR +} from "@angular/forms"; +import {MatSort} from "@angular/material/sort"; +import {MatDialog} from "@angular/material/dialog"; +import {MatPaginator} from "@angular/material/paginator"; +import {DocumentVersionRo} from "../../model/document-version-ro.model"; +import { + BeforeLeaveGuard +} from "../../../window/sidenav/navigation-on-leave-guard"; +import {GlobalLookups} from "../../global-lookups"; + +/** + * Component to display the properties of a document in a table. The properties can be edited and saved. + * @author Joze Rihtarsic + * @since 5.1 + */ +@Component({ + selector: 'document-versions-panel', + templateUrl: './document-versions-panel.component.html', + styleUrls: ['./document-versions-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DocumentVersionsPanelComponent), + multi: true + } + ] +}) +export class DocumentVersionsPanelComponent implements AfterViewInit, BeforeLeaveGuard, ControlValueAccessor { + @Output() selectedVersionChange: EventEmitter<number> = new EventEmitter<number>(); + + displayedColumns: string[] = ['version', 'status', 'createdOn', 'lastUpdatedOn']; + private onChangeCallback: (_: any) => void = () => { + }; + versionDataSource: MatTableDataSource<DocumentVersionRo> = new MatTableDataSource(); + dataChanged: boolean = false; + selected: DocumentVersionRo; + _currentVersion: number; + + @ViewChild("DocumentVersionsTable") table: MatTable<DocumentVersionRo>; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + constructor( + private globalLookups: GlobalLookups, + public dialog: MatDialog, + private controlContainer: ControlContainer) { + } + + get dateTimeFormat(): string { + return this.globalLookups.getDateTimeFormat(); + } + + @Input() set selectedVersion(version: number) { + this._currentVersion = version; + // find selected version + this.updateSelectedVersion(); + } + + get selectedVersion(): number { + return this._currentVersion; + } + + /** + * Private method to locate selected row for current version + * @private + */ + private updateSelectedVersion(): void { + + const selectedVersion :DocumentVersionRo = this.versionDataSource.data.find(v => v.version === this._currentVersion); + this.selected = selectedVersion; + } + + + + /** + * Method to handle row selection + * @param row + */ + onRowSelect(row: DocumentVersionRo) { + this.selected = row; + this.selectedVersionChange.emit(row.version); + } + + + @ViewChild(FormControlDirective, {static: true}) + formControlDirective: FormControlDirective; + @Input() + formControl: FormControl; + + @Input() + formControlName: string; /* get hold of FormControl instance no matter formControl or formControlName is given. If formControlName is given, then this.controlContainer.control is the parent FormGroup (or FormArray) instance. */ + get control() { + return this.formControl || this.controlContainer.control.get(this.formControlName); + } + + /** + * Implementation of the ControlValueAccessor method to write value to the component. + * @param eventList + */ + writeValue(eventList: DocumentVersionRo[]): void { + this.versionDataSource.data = !eventList?.length ? [] : [...eventList]; + this.updateSelectedVersion(); + this.dataChanged = false; + } + + ngAfterViewInit() { + this.versionDataSource.paginator = this.paginator; + this.versionDataSource.sort = this.sort; + } + + applyFilter(event: Event) { + const filterValue: string = (event.target as HTMLInputElement).value; + this.versionDataSource.filter = filterValue?.trim().toLowerCase(); + + if (this.versionDataSource.paginator) { + this.versionDataSource.paginator.firstPage(); + } + } + + isDirty(): boolean { + return this.dataChanged; + } + + registerOnChange(fn: any): void { + this.onChangeCallback = fn; + + } + + registerOnTouched(fn: any): void { + // not implemented + } + + setDisabledState(isDisabled: boolean): void { + // not implemented + } + + getRowClass(row, oddRow: boolean) { + return { + 'datatable-row-selected': row === this.selected, + 'datatable-row-odd': oddRow + }; + } + +} diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.html b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.html new file mode 100644 index 0000000000000000000000000000000000000000..16e7ffacebb0cefa0415bde66106d566a2bec681 --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.html @@ -0,0 +1,4 @@ +<div #root style="display: none;flex-direction: column"> + <div class="expandable-item-title">{{title}}</div> + <ng-content></ng-content> +</div> diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.scss b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..292fbbf6eba80e1abcbd1d504af8a19ff50fbbe0 --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.scss @@ -0,0 +1,10 @@ +.expandable-item-title { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + padding: 2px 2px 2px 10px; + border-bottom: 1px solid #e0e0e0; + width: 100%; + +} diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.ts b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab38db9e37fdbec975e79c843ec94bcbc7dc665f --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-item-component/expandable-item.component.ts @@ -0,0 +1,50 @@ +/*- + * #START_LICENSE# + * smp-webapp + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +import {Component, Input, ViewChild,} from '@angular/core'; + + +/** + * Component to display the properties of a document in a table. The properties can be edited and saved. + * @author Joze Rihtarsic + * @since 5.1 + */ +@Component({ + selector: 'expandable-item', + templateUrl: './expandable-item.component.html', + styleUrls: ['./expandable-item.component.scss'], +}) +export class ExpandableItemComponent { + + @Input() title: string; + @Input() buttonLabel: string; + @Input() icon: string; + @Input() tooltip: string; + + @ViewChild('root') expandableItem: any; + + constructor() { + + } + + showItem(show: boolean) { + this.expandableItem.nativeElement.style.display = show ? 'block' : 'none'; + } + + +} diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.html b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d73712f802c86499f80d393d5ae48514ba4a6340 --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.html @@ -0,0 +1,25 @@ +<div style="display: flex;flex-direction: row;height: 100%"> + <div *ngIf="expandPanel" id="expand-panel_id"> + <ng-content></ng-content> + </div> + <!-- table Toolbar --> + <div style="width: 42px"> + <button mat-mini-fab + attr.aria-label="{{ 'expandable.panel.label.expand.collapse' | translate }}" + matTooltip="{{ 'document.properties.panel.tooltip.expand.collapse' | translate }}" + (click)="onToggleExpandButtonClicked()"> + <mat-icon *ngIf="expandPanel">chevron_left</mat-icon> + <mat-icon *ngIf="!expandPanel">chevron_right</mat-icon> + </button> + <tool-button-spacer [vertical]="false"></tool-button-spacer> + <button *ngFor="let item of expandableItems; let i = index;" + [class]="getButtonClass(i)" + (click)="selectItem(item, i)" + [matTooltip]="item.title" + > + <mat-icon *ngIf="item.icon">{{ item.icon }}</mat-icon> + <span *ngIf="showButtonLabel" + class="vertical-button-label">{{item.buttonLabel}}</span> + </button> + </div> +</div> diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.scss b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..dcc099038e537562550909e7c49f647368335edf --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.scss @@ -0,0 +1,20 @@ +#expand-panel_id { + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 0 1em; + min-width: 600px; +} + +.vertical-button-label { + font-size: 0.8em; + min-height: 100px; + writing-mode: vertical-rl; +} + +.button-deselected { + margin: 1px 3px !important; + background: transparent; + border-style: none; +} + diff --git a/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.ts b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d91ada553e0fd249111c8bb55eb987fb4b1bdbd4 --- /dev/null +++ b/smp-angular/src/app/common/panels/expandable-panel-component/expandable-panel.component.ts @@ -0,0 +1,82 @@ +/*- + * #START_LICENSE# + * smp-webapp + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +import { + AfterViewInit, + Component, + ContentChildren, + Input, + QueryList, +} from '@angular/core'; +import { + ExpandableItemComponent +} from "./expandable-item-component/expandable-item.component"; + +/** + * Component to display the properties of a document in a table. The properties can be edited and saved. + * @author Joze Rihtarsic + * @since 5.1 + */ +@Component({ + selector: 'expandable-panel', + templateUrl: './expandable-panel.component.html', + styleUrls: ['./expandable-panel.component.scss'], +}) +export class ExpandablePanelComponent implements AfterViewInit { + + @ContentChildren(ExpandableItemComponent) private _expandableItems: QueryList<ExpandableItemComponent>; + @Input() showButtonLabel: boolean = false; + expandPanel: boolean = true; + selectedIndex: number = 0; + + constructor() { + + } + + ngAfterViewInit(): void { + this.updateShowItem() + } + + get expandableItems(): ExpandableItemComponent[] { + return this._expandableItems?.toArray(); + } + + public onToggleExpandButtonClicked(): void { + this.expandPanel = !this.expandPanel; + } + + selectItem(item: ExpandableItemComponent, index: number): void { + this.selectedIndex = index; + this.updateShowItem(); + } + + // show item at index and hide all others + updateShowItem(): void { + if (!this._expandableItems) { + return; + } + this._expandableItems.forEach((item: ExpandableItemComponent, i: number) => { + item.showItem(i === this.selectedIndex); + }); + } + + getButtonClass(index: number) { + return index === this.selectedIndex ? 'mat-raised-button' : 'mat-raised-button button-deselected'; + } + +} diff --git a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.html b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.html index d4ed1406ec4deadcd229d3dedacbe946b1c3e0d3..42bebb727f9f8ba464221529dd083c709943ee1e 100644 --- a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.html +++ b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.html @@ -58,6 +58,11 @@ <td mat-cell *matCellDef="let row">{{row.roleType}}</td> </ng-container> + <ng-container matColumnDef="hasPermissionToReview"> + <th mat-header-cell *matHeaderCellDef>{{ "membership.panel.label.permission.review" | translate }}</th> + <td mat-cell *matCellDef="let row">{{row.hasPermissionReview}}</td> + </ng-container> + <ng-container matColumnDef="memberOf"> <th mat-header-cell *matHeaderCellDef>{{ "membership.panel.label.member.of" | translate }}</th> <td mat-cell *matCellDef="let row">{{row.memberOf}}</td> diff --git a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts index e6673916de4f94a493d22b90cd119c2b7f3d9f5d..c3ea322dfeec2a4a5f1a4bb6f4c440c26d8a675c 100644 --- a/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts +++ b/smp-angular/src/app/common/panels/membership-panel/membership-panel.component.ts @@ -30,12 +30,14 @@ export class MembershipPanelComponent implements BeforeLeaveGuard { pageSize: number = 10; @Input() membershipType: MemberTypeEnum = MemberTypeEnum.DOMAIN; + private _domain: DomainRo; private _group: GroupRo; private _resource: ResourceRo; - displayedColumns: string[] = ['username', 'fullName', 'roleType', 'memberOf']; + _displayedColumns: string[] = ['username', 'fullName', 'roleType', 'memberOf']; + data: MemberRo[] = []; selectedMember: MemberRo; filter: any = {}; @@ -77,6 +79,17 @@ export class MembershipPanelComponent implements BeforeLeaveGuard { return this.resultsLength; } + public get displayedColumns(): string[] { + switch (this.membershipType) { + case MemberTypeEnum.DOMAIN: + return ['username', 'fullName', 'roleType']; + case MemberTypeEnum.GROUP: + return ['username', 'fullName', 'roleType']; + case MemberTypeEnum.RESOURCE: + return ['username', 'fullName', 'roleType', 'hasPermissionToReview']; + } + } + @Input() set domain(value: DomainRo) { this._domain = value; if (!!value) { diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.html b/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..96e810bbeec61dbb082c1985fb76d5461c0c4473 --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.html @@ -0,0 +1,3 @@ +<document-edit-panel #reviewDocumentEditor + editorMode="REVIEW_EDITOR"> +</document-edit-panel> diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.scss b/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.ts b/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..40b21eee818a255b8d760b3b8326acba6db9ec78 --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-document-panel/review-document-panel.component.ts @@ -0,0 +1,25 @@ +import {Component, ViewChild, ViewEncapsulation,} from '@angular/core'; +import { + BeforeLeaveGuard +} from "../../../../window/sidenav/navigation-on-leave-guard"; +import { + DocumentEditPanelComponent +} from "../../document-edit-panel/document-edit-panel.component"; + +@Component({ + templateUrl: './review-document-panel.component.html', + styleUrls: ['./review-document-panel.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class ReviewDocumentPanelComponent implements BeforeLeaveGuard { + + @ViewChild('reviewDocumentEditor') documentEditor: DocumentEditPanelComponent; + + constructor() { + } + + isDirty(): boolean { + return false; + } + +} diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-controller.ts b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1104a4adca67bb41ecaeb034d150db6bc3402f9 --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-controller.ts @@ -0,0 +1,55 @@ +import { + SearchTableController +} from '../../search-table/search-table-controller'; +import {MatDialog, MatDialogRef} from '@angular/material/dialog'; +import {GlobalLookups} from "../../global-lookups"; +import {SearchTableEntity} from "../../search-table/search-table-entity.model"; +import { + ObjectPropertiesDialogComponent +} from "../../dialogs/object-properties-dialog/object-properties-dialog.component"; + +export class ReviewTasksController implements SearchTableController { + + constructor(protected lookups: GlobalLookups, public dialog: MatDialog) { + } + + validateDeleteOperation(rows: SearchTableEntity[]) { + return null; + } + + newRow(): SearchTableEntity { + return null; + } + + dataSaved() { + + } + + isRecordChanged(oldModel: any, newModel: any): boolean { + return false; + } + + isRowExpanderDisabled(row: SearchTableEntity): boolean { + return true; + } + + public showDetails(row: any): MatDialogRef<any> { + return this.dialog.open(ObjectPropertiesDialogComponent, { + data: { + title: "Review tasks details", + object: row, + } + }); + } + + public edit(row: any): MatDialogRef<any> { + return null; + } + + public delete(row: any) { + } + + newDialog(config): MatDialogRef<any> { + return null; + } +} diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.css b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.css new file mode 100644 index 0000000000000000000000000000000000000000..df34c7d7f1a359fa3f6ca5e7c1eea2cb4fd74114 --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.css @@ -0,0 +1,41 @@ +/* --- Select ---*/ +.mat-select{ + padding:20px 0; +} + +/* --- Button ---*/ +.group-btn { + margin-top:20px; +} + +#hiddenButtonId { + position: fixed; +} + +::ng-deep .missingKey { + text-decoration: line-through !important; + font-weight: bold; + color:red; +} + +::ng-deep .deleted { + text-decoration: line-through !important; + font-weight: bold; +} +::ng-deep .table-row-new { + + color: darkgreen !important; + font-weight: bold; +} +::ng-deep .table-row-updated { + font-weight: bold; +} +::ng-deep .table-row { + font-weight: normal; +} + +.truncate-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.html b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.html new file mode 100644 index 0000000000000000000000000000000000000000..2c4902dd3b7383616cec6b4b546ae50e38d006f7 --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.html @@ -0,0 +1,21 @@ +<smp-search-table + #searchTable + [columnPicker]="columnPicker" + [url]="baseUrl" + [additionalToolButtons]="additionalToolButtons" + [searchTableController]="reviewTaskController" + [showSearchPanel]="false" + [filter]="filter" + [allowNewItems]="false" + [allowDeleteItems]="false" + [allowEditItems]="true" + [showActionButtons]="false" + (onRowDoubleClicked)="onRowDoubleClicked($event)" +> + <ng-template #additionalToolButtons> + <span style="width: 2px"> </span> + </ng-template> + <ng-template #dateTimeColumn let-value="value" ngx-datatable-cell-template> + <div class='truncate-text' title="{{value | date:dateTimeFormat}}">{{ value | date:dateTimeFormat }}</div> + </ng-template> +</smp-search-table> diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.ts b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..492db70b46db4f313294502a7f2c40b4052094ca --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-panel.component.ts @@ -0,0 +1,174 @@ +import { + AfterViewChecked, + AfterViewInit, + ChangeDetectorRef, + Component, + Input, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; +import {ColumnPicker} from '../../column-picker/column-picker.model'; +import {MatDialog} from '@angular/material/dialog'; + +import {AlertMessageService} from '../../alert-message/alert-message.service'; +import {HttpClient} from '@angular/common/http'; +import {GlobalLookups} from "../../global-lookups"; +import {SearchTableComponent} from "../../search-table/search-table.component"; +import {SecurityService} from "../../../security/security.service"; +import {ReviewTasksController} from "./review-tasks-controller"; +import {lastValueFrom} from "rxjs"; +import {TranslateService} from "@ngx-translate/core"; +import { + NavigationNode, + NavigationService +} from "../../../window/sidenav/navigation-model.service"; +import { + EditResourceService +} from "../../../edit/edit-resources/edit-resource.service"; +import { + ReviewDocumentVersionRo +} from "../../model/review-document-version-ro.model"; + +/** + * This is a generic alert panel component for previewing alert list + */ +@Component({ + selector: 'review-tasks-panel', + templateUrl: './review-tasks-panel.component.html', + styleUrls: ['./review-tasks-panel.component.css'] +}) +export class ReviewTasksPanelComponent implements OnInit, AfterViewInit, AfterViewChecked { + + @ViewChild('rowMetadataAction') rowMetadataAction: TemplateRef<any>; + @ViewChild('rowActions') rowActions: TemplateRef<any>; + @ViewChild('searchTable') searchTable: SearchTableComponent; + @ViewChild('dateTimeColumn') dateTimeColumn: TemplateRef<any>; + @ViewChild('truncateText') truncateText: TemplateRef<any>; + @ViewChild('credentialType') credentialType: TemplateRef<any>; + @ViewChild('forUser') forUser: TemplateRef<any>; + + + @Input() baseUrl = null; + columnPicker: ColumnPicker = new ColumnPicker(); + reviewTaskController: ReviewTasksController; + filter: any = {}; + selected: any; + + constructor(public securityService: SecurityService, + protected lookups: GlobalLookups, + protected http: HttpClient, + protected alertService: AlertMessageService, + private translateService: TranslateService, + public dialog: MatDialog, + private changeDetector: ChangeDetectorRef, + private navigationService: NavigationService, + private editResourceService: EditResourceService,) { + } + + ngOnInit() { + this.reviewTaskController = new ReviewTasksController(this.lookups, this.dialog); + } + + ngAfterViewChecked() { + this.changeDetector.detectChanges(); + } + + async initColumns() { + this.columnPicker.allColumns = [ + { + name: 'Review date', + title: "Review date", + prop: 'lastUpdatedOn', + showInitially: true, + maxWidth: 200, + cellTemplate: this.dateTimeColumn, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.target")), + prop: 'target', + maxWidth: 160, + showInitially: true, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.version")), + prop: 'version', + maxWidth: 60, + showInitially: true, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.resource.scheme")), + prop: 'resourceIdentifierScheme', + width: 250, + maxWidth: 250, + resizable: 'true', + showInitially: true, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.resource.value")), + prop: 'resourceIdentifierValue', + resizable: 'true', + showInitially: true, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.subresource.scheme")), + prop: 'subresourceIdentifierScheme', + width: 250, + maxWidth: 250, + resizable: 'true', + showInitially: true, + }, + { + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.column.subresource.value")), + prop: 'subresourceIdentifierValue', + resizable: 'true', + showInitially: true, + }, + + + + ]; + this.columnPicker.selectedColumns = this.columnPicker.allColumns.filter(col => col.showInitially); + this.searchTable.tableColumnInit(); + } + + ngAfterViewInit() { + this.initColumns(); + } + + + details(row: any) { + } + + // for dirty guard... + isDirty(): boolean { + return this.searchTable.isDirty(); + } + + get dateTimeFormat(): string { + return this.lookups.getDateTimeFormat(); + } + + async onRowDoubleClicked(row: ReviewDocumentVersionRo) { + // set selected resource + this.editResourceService.selectedReviewDocument = row; + let node: NavigationNode = await this.createNewReviewDocumentNavigationNode(); + this.navigationService.selected.children = [node] + this.navigationService.select(node); + + } + + public async createNewReviewDocumentNavigationNode() { + return { + code: "review-document", + icon: "note", + name: await lastValueFrom(this.translateService.get("review.edit.panel.label.review")), + routerLink: "review-document", + selected: true, + tooltip: "", + transient: true, + i18n: "navigation.label.edit.document.review" + } + } +} + diff --git a/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-ro.model.ts b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-ro.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..67af8e91fdfbc070bc7c89d0596d9debdc412f8a --- /dev/null +++ b/smp-angular/src/app/common/panels/review-tasks-panel/review-tasks-ro.model.ts @@ -0,0 +1,14 @@ +import {SearchTableEntity} from '../../search-table/search-table-entity.model'; + +export interface AlertRo extends SearchTableEntity { + sid: string; + alertType: string; + alertStatus: string; + alertStatusDesc?:string; + alertLevel: string; + processedTime?: Date; + reportingTime: Date; + mailTo?:string; + alertDetails?: Object; +} + diff --git a/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.html b/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.html index 8430ae6435f924e0ff2179326dbb1e5226d37516..5d9cec86d2a07718c0d696e4421934aab26a0ae4 100644 --- a/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.html +++ b/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.html @@ -64,6 +64,7 @@ (change)="onLocaleSelect($event.target.value)" formControlName="smpLocale" > + <!-- currently supported locales see the main.ts--> <option value="bg">{{ "user.profile.panel.label.language.bg" | translate }}</option> <option value="cs">{{ "user.profile.panel.label.language.cs" | translate }}</option> <option value="da">{{ "user.profile.panel.label.language.da" | translate }}</option> diff --git a/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.ts b/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.ts index 186d76bd9e4b4d566fd298b79d4ac09032d8f88e..19c2b6ad317f939f9b61d567365e22c49fe2fb56 100644 --- a/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.ts +++ b/smp-angular/src/app/common/panels/user-settings-panel/user-profile-panel.component.ts @@ -1,5 +1,11 @@ -import {Component, ElementRef, EventEmitter, Input, Output, ViewChild,} from '@angular/core'; -import {SmpConstants} from "../../../smp.constants"; +import { + Component, + ElementRef, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core'; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {SecurityService} from "../../../security/security.service"; import {ThemeService} from "../../theme-service/theme.service"; @@ -11,7 +17,6 @@ import {NgxMatDateAdapter} from "@angular-material-components/datetime-picker"; import {ApplicationRoleEnum} from "../../enums/application-role.enum"; import {UserRo} from "../../model/user-ro.model"; import {UserController} from "../../services/user-controller"; -import {TranslateService} from "@ngx-translate/core"; @Component({ selector: 'user-profile-panel', @@ -25,9 +30,6 @@ export class UserProfilePanelComponent { @Output() onChangeUserPasswordEvent: EventEmitter<UserRo> = new EventEmitter(); readonly emailPattern = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'; - readonly dateFormat: string = 'yyyy-MM-dd HH:mm:ssZ'; - readonly dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; - readonly nullValue: string = SmpConstants.NULL_VALUE; readonly applicationRoles = Object.keys(ApplicationRoleEnum).map(el => { return {key: el, value: ApplicationRoleEnum[el]} @@ -62,7 +64,10 @@ export class UserProfilePanelComponent { 'username': new FormControl({value: '', disabled: true}), 'role': new FormControl({value: '', disabled: true}), 'active': new FormControl({value: '', disabled: true}), - 'emailAddress': new FormControl({value: '', disabled: false}, [Validators.pattern(this.emailPattern), + 'emailAddress': new FormControl({ + value: '', + disabled: false + }, [Validators.pattern(this.emailPattern), Validators.maxLength(255)]), 'fullName': new FormControl({value: '', disabled: false}), 'smpTheme': new FormControl({value: 'default_theme', disabled: false}), @@ -73,7 +78,10 @@ export class UserProfilePanelComponent { this.userCredentialForm = formBuilder.group({ 'passwordUpdatedOn': new FormControl({value: '', disabled: true}), 'passwordExpireOn': new FormControl({value: '', disabled: true}), - 'sequentialLoginFailureCount': new FormControl({value: '0', disabled: true}), + 'sequentialLoginFailureCount': new FormControl({ + value: '0', + disabled: true + }), 'lastFailedLoginAttempt': new FormControl({value: '', disabled: true}), 'suspendedUtil': new FormControl({value: '', disabled: true}), }); @@ -131,7 +139,6 @@ export class UserProfilePanelComponent { } - private updatePwdCredential(value: UserRo) { // form is always disabled this.userCredentialForm.disable() @@ -144,7 +151,7 @@ export class UserProfilePanelComponent { } else { this.userCredentialForm.controls['passwordUpdatedOn'].setValue(value.passwordUpdatedOn); this.userCredentialForm.controls['passwordExpireOn'].setValue(value.passwordExpireOn); - this.userCredentialForm.controls['sequentialLoginFailureCount'].setValue(!(value.sequentialLoginFailureCount)?"---":value.sequentialLoginFailureCount); + this.userCredentialForm.controls['sequentialLoginFailureCount'].setValue(!(value.sequentialLoginFailureCount) ? "---" : value.sequentialLoginFailureCount); this.userCredentialForm.controls['lastFailedLoginAttempt'].setValue(value.lastFailedLoginAttempt); this.userCredentialForm.controls['suspendedUtil'].setValue(value.suspendedUtil); } @@ -215,11 +222,11 @@ export class UserProfilePanelComponent { return !this._managedUserData?.userId; } - get canChangeRole ():boolean { + get canChangeRole(): boolean { return !this.isUserDataLoggedInUserData } - get isUserDataLoggedInUserData(){ + get isUserDataLoggedInUserData() { return this.securityService.getCurrentUser()?.userId == this._managedUserData?.userId } diff --git a/smp-angular/src/app/common/search-table/search-table-controller.ts b/smp-angular/src/app/common/search-table/search-table-controller.ts index 4d0961431a36d92a64bd163a8559f64ee7f8e52c..01e6770778c65d078d8e167cf623068d58378107 100644 --- a/smp-angular/src/app/common/search-table/search-table-controller.ts +++ b/smp-angular/src/app/common/search-table/search-table-controller.ts @@ -25,4 +25,5 @@ export interface SearchTableController { * @param row the row for which the row expander should be disabled or not */ isRowExpanderDisabled(row: SearchTableEntity): boolean; + } diff --git a/smp-angular/src/app/common/search-table/search-table.component.ts b/smp-angular/src/app/common/search-table/search-table.component.ts index 758ee14828f07fa8fd261d26660499e8a08919e8..095324ad0f63b80acee062693ed537406dd280ba 100644 --- a/smp-angular/src/app/common/search-table/search-table.component.ts +++ b/smp-angular/src/app/common/search-table/search-table.component.ts @@ -1,4 +1,11 @@ -import {Component, Input, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import { + Component, EventEmitter, + Input, + OnInit, + Output, + TemplateRef, + ViewChild +} from '@angular/core'; import {SearchTableResult} from './search-table-result.model'; import {lastValueFrom, Observable} from 'rxjs'; import {AlertMessageService} from '../alert-message/alert-message.service'; @@ -9,12 +16,20 @@ import {SearchTableController} from './search-table-controller'; import {finalize} from 'rxjs/operators'; import {SearchTableEntity} from './search-table-entity.model'; import {EntityStatus} from '../enums/entity-status.enum'; -import {CancelDialogComponent} from '../dialogs/cancel-dialog/cancel-dialog.component'; -import {SaveDialogComponent} from '../dialogs/save-dialog/save-dialog.component'; +import { + CancelDialogComponent +} from '../dialogs/cancel-dialog/cancel-dialog.component'; +import { + SaveDialogComponent +} from '../dialogs/save-dialog/save-dialog.component'; import {DownloadService} from '../../download/download.service'; import {HttpParams} from '@angular/common/http'; -import {ConfirmationDialogComponent} from "../dialogs/confirmation-dialog/confirmation-dialog.component"; -import {SearchTableValidationResult} from "./search-table-validation-result.model"; +import { + ConfirmationDialogComponent +} from "../dialogs/confirmation-dialog/confirmation-dialog.component"; +import { + SearchTableValidationResult +} from "./search-table-validation-result.model"; import {ExtendedHttpClient} from "../../http/extended-http-client"; import {Router} from "@angular/router"; import ObjectUtils from "../utils/object-utils"; @@ -26,6 +41,8 @@ import {TranslateService} from "@ngx-translate/core"; styleUrls: ['./search-table.component.css'] }) export class SearchTableComponent implements OnInit { + @Output() onRowDoubleClicked: EventEmitter<SearchTableEntity> = new EventEmitter<SearchTableEntity>(); + @ViewChild('searchTable', {static: true}) searchTable: any; @ViewChild('rowActions', {static: true}) rowActions: TemplateRef<any>; @ViewChild('rowExpand', {static: true}) rowExpand: TemplateRef<any>; @@ -71,18 +88,18 @@ export class SearchTableComponent implements OnInit { forceRefresh: boolean = false; showSpinner: boolean = false; currentResult: SearchTableResult = null; - // override datatable messages to remove selectedMessage message - datatableMessages: any = { - // Message to show when array is presented - // but contains no values - emptyMessage: 'No data to display', + // override datatable messages to remove selectedMessage message + datatableMessages: any = { + // Message to show when array is presented + // but contains no values + emptyMessage: 'No data to display', - // Footer total message - totalMessage: 'total', + // Footer total message + totalMessage: 'total', - // Footer selected message - selectedMessage: null -}; + // Footer selected message + selectedMessage: null + }; constructor(protected http: ExtendedHttpClient, protected alertService: AlertMessageService, @@ -121,7 +138,7 @@ export class SearchTableComponent implements OnInit { } - tableColumnInit(){ + tableColumnInit() { // Add actions to last column if (this.columnPicker) { // prepend columns @@ -242,6 +259,7 @@ export class SearchTableComponent implements OnInit { onActivate(event) { if ("dblclick" === event.type) { + this.onRowDoubleClicked.emit(event.row); this.editSearchTableEntityRow(event.row); } } @@ -256,7 +274,7 @@ export class SearchTableComponent implements OnInit { onNewButtonClicked() { - this.fireCreateNewEntityEvent(); + this.fireCreateNewEntityEvent(); } fireCreateNewEntityEvent() { @@ -277,7 +295,7 @@ export class SearchTableComponent implements OnInit { } onDeleteButtonClicked() { - this.fireDeleteEntityEvent(); + this.fireDeleteEntityEvent(); } fireDeleteEntityEvent() { @@ -285,11 +303,11 @@ export class SearchTableComponent implements OnInit { } onDeleteRowActionClicked(row: SearchTableEntity) { - this.deleteSearchTableEntities([row]); + this.deleteSearchTableEntities([row]); } onEditButtonClicked() { - this.fireEditEntityEvent(); + this.fireEditEntityEvent(); } fireEditEntityEvent() { @@ -357,7 +375,8 @@ export class SearchTableComponent implements OnInit { getRowsAsString(): number { return this.rows.length; } - getCurrentResult(){ + + getCurrentResult() { return this.currentResult; } @@ -366,7 +385,7 @@ export class SearchTableComponent implements OnInit { } get managementUrl(): string { - return (this.manageUrl == null || this.manageUrl.length === 0)? this.url:this.manageUrl; + return (this.manageUrl == null || this.manageUrl.length === 0) ? this.url : this.manageUrl; } get deleteButtonEnabled(): boolean { @@ -402,7 +421,10 @@ export class SearchTableComponent implements OnInit { const status = ObjectUtils.isEqual(row.status, EntityStatus.PERSISTED) ? EntityStatus.UPDATED : row.status; - this.rows[rowNumber] = {...formRef.componentInstance.getCurrent(), status}; + this.rows[rowNumber] = { + ...formRef.componentInstance.getCurrent(), + status + }; this.rows = [...this.rows]; } } diff --git a/smp-angular/src/app/common/utils/string-utils.ts b/smp-angular/src/app/common/utils/string-utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..59b99a34d3c9566b59418d605571dc5b93b65215 --- /dev/null +++ b/smp-angular/src/app/common/utils/string-utils.ts @@ -0,0 +1,35 @@ +/** + * String utils + */ +export default class StringUtils { + /** + * Capitalize the first letter of the string + * @param value + * @returns {string} Capitalized string + */ + static capitalizeFirst(value: string): string { + if (value === null) { + return 'Not assigned'; + } + return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(); + } + + /** + * Format string + * @param format - the format as example: 'Hello {1} {0}' + * @param parameters - the parameters as example: ['Janez', 'Novak'] + * @returns {string} Formatted string as example: 'Hello Novak Janez' + */ + static format(format: string, parameters: string[]): string { + if (parameters) { + format = format.replace(/\{([^}]+)}/g, function (match, key) { + return (parameters != null && key in parameters) ? parameters[key] : match; + }); + } + return format; + } + + static isEmpty(str): boolean { + return (!str || 0 === str.length); + } +} diff --git a/smp-angular/src/app/edit/edit-group/edit-group.service.ts b/smp-angular/src/app/edit/edit-group/edit-group.service.ts index 82c952e3a3c99dbb7413af86e0cdce803d54a63a..325143cb43542456d2d782f4fdf1758643fa4e99 100644 --- a/smp-angular/src/app/edit/edit-group/edit-group.service.ts +++ b/smp-angular/src/app/edit/edit-group/edit-group.service.ts @@ -13,7 +13,6 @@ import {DomainRo} from "../../common/model/domain-ro.model"; @Injectable() export class EditGroupService { - constructor( private http: HttpClient, private securityService: SecurityService) { @@ -79,14 +78,4 @@ export class EditGroupService { .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId) .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId), resource); } - - updateResourceForGroup(resource: ResourceRo, group: GroupRo, domain: DomainRo): Observable<ResourceRo> { - const currentUser: User = this.securityService.getCurrentUser(); - - return this.http.post<ResourceRo>(SmpConstants.REST_EDIT_RESOURCE_UPDATE - .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId) - .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), resource); - } } diff --git a/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.html b/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.html index 38d4cfcc1fa9ffea3e52e3fa2d00e2f33edeb610..2cbf4607ea9242d3deda34245839cc057b8c9367 100644 --- a/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.html +++ b/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.html @@ -19,7 +19,7 @@ <mat-label>{{ "resource.dialog.label.resource.id" | translate }}</mat-label> <input id="identifierValue_id" type="text" matInput #identifierValue formControlName="identifierValue" - required auto-focus-directive + auto-focus-directive maxlength="255" required> <div *ngIf="(newMode && resourceForm.controls['identifierValue'].touched ) && resourceForm.controls['identifierValue'].hasError('required')" @@ -51,7 +51,11 @@ {{participantSchemeMessage}} </div> </mat-form-field> - + <mat-checkbox formControlName="reviewEnabled" + matTooltip="{{ 'resource.dialog.label.resource.review.enabled' | translate }}" + id="reviewEnabled_id"> + {{ "resource.dialog.tooltip.resource.review.enabled" | translate }} + </mat-checkbox> <mat-form-field style="width:100%"> <mat-label>{{ "resource.dialog.label.resource.visibility" | translate }}</mat-label> diff --git a/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.ts b/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.ts index 6ed953ced52e4dcce9b9e5bc81252007d3511ecc..1c4d4153fb15e16be026dcae266d7ec863075996 100644 --- a/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.ts +++ b/smp-angular/src/app/edit/edit-group/group-resource-panel/resource-dialog/resource-dialog.component.ts @@ -1,14 +1,21 @@ import {Component, ElementRef, Inject, Input, ViewChild} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; -import {AlertMessageService} from "../../../../common/alert-message/alert-message.service"; +import { + AlertMessageService +} from "../../../../common/alert-message/alert-message.service"; import {VisibilityEnum} from "../../../../common/enums/visibility.enum"; import {GroupRo} from "../../../../common/model/group-ro.model"; import {ResourceRo} from "../../../../common/model/resource-ro.model"; import {DomainRo} from "../../../../common/model/domain-ro.model"; -import {ResourceDefinitionRo} from "../../../../system-settings/admin-extension/resource-definition-ro.model"; +import { + ResourceDefinitionRo +} from "../../../../system-settings/admin-extension/resource-definition-ro.model"; import {EditGroupService} from "../../edit-group.service"; import {GlobalLookups} from "../../../../common/global-lookups"; +import { + EditResourceService +} from "../../../edit-resources/edit-resource.service"; @Component({ @@ -18,7 +25,7 @@ import {GlobalLookups} from "../../../../common/global-lookups"; export class ResourceDialogComponent { readonly groupVisibilityOptions = Object.keys(VisibilityEnum) - .map(el => { + .map(el => { return {key: el, value: VisibilityEnum[el]} }); @@ -28,18 +35,20 @@ export class ResourceDialogComponent { messageType: string = "alert-error"; group: GroupRo; _resource: ResourceRo - domain:DomainRo; - domainResourceDefs:ResourceDefinitionRo[]; + domain: DomainRo; + domainResourceDefs: ResourceDefinitionRo[]; participantSchemePattern = '^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$'; - participantSchemeMessage:string; - submitInProgress:boolean = false; + participantSchemeMessage: string; + submitInProgress: boolean = false; @ViewChild('identifierValue', {static: false}) identifierValue: ElementRef; + constructor(@Inject(MAT_DIALOG_DATA) public data: any, public lookups: GlobalLookups, public dialogRef: MatDialogRef<ResourceDialogComponent>, private editGroupService: EditGroupService, + private editResourceService: EditResourceService, private alertService: AlertMessageService, private formBuilder: FormBuilder ) { @@ -56,9 +65,10 @@ export class ResourceDialogComponent { this.resourceForm = formBuilder.group({ - 'identifierValue': new FormControl({value: null}, ), - 'identifierScheme': new FormControl({value: null},[Validators.pattern(this.participantSchemePattern)]), + 'identifierValue': new FormControl({value: null},), + 'identifierScheme': new FormControl({value: null}, [Validators.pattern(this.participantSchemePattern)]), 'visibility': new FormControl({value: null}), + 'reviewEnabled': new FormControl({value: null}), 'resourceTypeIdentifier': new FormControl({value: null}), '': new FormControl({value: null}) }); @@ -106,6 +116,7 @@ export class ResourceDialogComponent { } this.resourceForm.controls['visibility'].setValue(value.visibility); + this.resourceForm.controls['reviewEnabled'].setValue(value.reviewEnabled); } else { this.resourceForm.disable(); @@ -144,22 +155,8 @@ export class ResourceDialogComponent { public createResource(resource: ResourceRo) { - this.submitInProgress = true; - this.editGroupService.createResourceForGroup(this.resource, this.group, this.domain).subscribe((result: ResourceRo) => { - if (!!result) { - this.closeDialog(); - } - this.submitInProgress = false; - }, (error) => { - this.alertService.error(error.error?.errorDescription) - this.submitInProgress = false; - }); - - } - - public saveResource(resource: ResourceRo) { this.submitInProgress = true; - this.editGroupService.updateResourceForGroup(this.resource, this.group, this.domain).subscribe((result: ResourceRo) => { + this.editGroupService.createResourceForGroup(this.resource, this.group, this.domain).subscribe((result: ResourceRo) => { if (!!result) { this.closeDialog(); } @@ -168,6 +165,22 @@ export class ResourceDialogComponent { this.alertService.error(error.error?.errorDescription) this.submitInProgress = false; }); + + } + + public saveResource(resource: ResourceRo): void { + this.submitInProgress = true; + this.editResourceService.updateResourceForGroup(resource, this.group, this.domain).subscribe({ + next: (result: ResourceRo): void => { + if (!!result) { + this.closeDialog(); + } + this.submitInProgress = false; + }, error: (err: any): void => { + this.alertService.error(err.error?.errorDescription) + this.submitInProgress = false; + } + }); } public setFocus() { diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.controller.ts b/smp-angular/src/app/edit/edit-resources/edit-resource.controller.ts index a114b874c61b33b23561ac5c87f13079aa08f6fd..a9b699b438544fa229e9fdafc10800f173aed114 100644 --- a/smp-angular/src/app/edit/edit-resources/edit-resource.controller.ts +++ b/smp-angular/src/app/edit/edit-resources/edit-resource.controller.ts @@ -88,12 +88,12 @@ export class EditResourceController extends MatTableDataSource<ResourceRo>{ refreshDomains() { this.isLoadingResults = true; this.domainService.getDomainsForResourceAdminUserObservable() - .subscribe((result: DomainRo[]) => { + .subscribe({next: (result: DomainRo[]) => { this.updateDomainList(result) - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) + }, error: (err: any) => { + this.alertService.error(err.error?.errorDescription) this.isLoadingResults = false; - }); + }}); } refreshGroups() { diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts index 3d83d8eb719811f5496b454a76b9901a2327e519..0076279d3ea0cb334737f24724628094e80fad9b 100644 --- a/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts +++ b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts @@ -11,6 +11,9 @@ import {TableResult} from "../../common/model/table-result.model"; import {DomainRo} from "../../common/model/domain-ro.model"; import {DocumentRo} from "../../common/model/document-ro.model"; import {SubresourceRo} from "../../common/model/subresource-ro.model"; +import { + ReviewDocumentVersionRo +} from "../../common/model/review-document-version-ro.model"; /** * The EditResourceService is used for server interaction on resources, sub-resources and it's documents. @@ -23,6 +26,7 @@ export class EditResourceService { selectedResource: ResourceRo; selectedSubresource: SubresourceRo; + selectedReviewDocument: ReviewDocumentVersionRo; constructor( private http: HttpClient, @@ -43,6 +47,22 @@ export class EditResourceService { return this.getGroupResourcesForUserTypeObservable('resource-admin', group, domain, filter, page, pageSize); } + /** + * Method allows group admin to update the resource properties + * @param resource + * @param group + * @param domain + */ + updateResourceForGroup(resource: ResourceRo, group: GroupRo, domain: DomainRo): Observable<ResourceRo> { + const currentUser: User = this.securityService.getCurrentUser(); + + return this.http.post<ResourceRo>(SmpConstants.REST_EDIT_RESOURCE_UPDATE + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_DOMAIN_ID, domain?.domainId) + .replace(SmpConstants.PATH_PARAM_ENC_GROUP_ID, group?.groupId) + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), resource); + } + /** * Method return observable of resource list from the server for resource-admin role for selected domain and group filter and paginating data. * @@ -83,99 +103,220 @@ export class EditResourceService { * @param version version of document - if null current version is returned. * @returns observable of DocumentRo */ - public getDocumentObservable(resource: ResourceRo, version: number = null): Observable<DocumentRo> { + public getResourceDocumentObservable(resource: ResourceRo, version: number = null): Observable<DocumentRo> { let params: HttpParams = null; if (version) { params = new HttpParams() .set('version', version); } const currentUser: User = this.securityService.getCurrentUser(); - return this.http.get<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT + return this.http.get<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_RESOURCE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), {params}); } /** - * Method submits the document to be saved to server and return observable of the saved Document object from the server. - * - * @param resource resource for which document belongs to. - * @param document document to be saved. + * Method return observable of Document object for subresource from the server. + * @param subresource subresource for which document is returned. + * @param resource resource of the subresource. + * @param version version of document - if null current version is returned. * @returns observable of DocumentRo */ - public saveDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + public getSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, version: number = null): Observable<DocumentRo> { + let params: HttpParams = null; + if (version) { + params = new HttpParams() + .set('version', version); + } const currentUser: User = this.securityService.getCurrentUser(); - return this.http.put<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT + return this.http.get<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), document); + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) + .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), {params}); } - public validateDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + /** + * Method returns observable for saving the document for resource to the server. + * + * @param resource resource for which document belongs to. + * @param document document to be saved. + * @returns observable of DocumentRo + */ + public saveResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_VALIDATE + return this.http.put<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_RESOURCE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), document); } - public generateDocumentObservable(resource: ResourceRo): Observable<DocumentRo> { + /** + * Method returns observable for saving the document for subresource to the server. + * + * @param subresource subresource for which document belongs to. + * @param resource parent resource of the subresource. + * @param document document to be saved. + * @returns observable of DocumentRo + */ + public saveSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_GENERATE + return this.http.put<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), null); + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) + .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), document); } - getSubResourcesForResource(resource: ResourceRo): Observable<SubresourceRo[]> { + /** + * Method returns observable for validating the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be validated. + * @returns document DocumentRo to be validated. + */ + public validateResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.get<SubresourceRo[]>(SmpConstants.REST_EDIT_SUBRESOURCE + return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_VALIDATE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId)); + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), document); } - deleteSubresourceFromResource(subResource: SubresourceRo, resource: ResourceRo): Observable<SubresourceRo> { + /** + * Method returns observable for validating the document for subresource on the server. + * @param subresource subresource for which document belongs to. + * @param resource parent resource of the subresource. + * @param document DocumentRo to be validated. + */ + public validateSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.delete<ResourceRo>(SmpConstants.REST_EDIT_SUBRESOURCE_DELETE + return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) - .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subResource?.subresourceId)); + .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), document); } - createSubResourceForResource(subresource: SubresourceRo, resource: ResourceRo): Observable<SubresourceRo> { - const currentUser: User = this.securityService.getCurrentUser(); - return this.http.put<SubresourceRo>(SmpConstants.REST_EDIT_SUBRESOURCE_CREATE - .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), - subresource); + /** + * Method returns observable for publishing the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be published. + */ + public publishResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.resourceDocumentActionObservable(resource, document, SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_PUBLISH); } + /** + * Method returns observable for publishing the document for subresource on the server. + * @param subresource subresource for which document belongs to. + * @param resource resource of the subresource. + * @param document document to be published. + */ + public publishSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.subresourceDocumentActionObservable(subresource, resource, document, SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_PUBLISH); + } - public getSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, version: number = null): Observable<DocumentRo> { - let params: HttpParams = null; - if (version) { - params = new HttpParams() - .set('version', version); - } + /** + * Method returns observable for review request of the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be reviewed. + */ + public reviewRequestForResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.resourceDocumentActionObservable(resource, document, SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_REVIEW_REQUEST); + } + + /** + * Method returns observable for review request of the document for subresource on the server. + * @param subresource subresource for which document belongs to. + * @param resource resource of the subresource. + * @param document document to be reviewed. + */ + public reviewRequestForSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.subresourceDocumentActionObservable(subresource, resource, document, SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_REQUEST); + } + + /** + * Method returns observable for review approve of the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be approved. + */ + public reviewApproveForResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.resourceDocumentActionObservable(resource, document, SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_REVIEW_APPROVE); + } + + /** + * Method returns observable for review approve of the document for resource on the server. + * @param subresource subresource for which document belongs to. + * @param resource resource for which document belongs to. + * @param document document to be approved. + */ + public reviewApproveForSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.subresourceDocumentActionObservable(subresource, resource, document, SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_APPROVE); + } + + /** + * Method returns observable for review reject of the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be rejected. + */ + public reviewRejectResourceDocumentObservable(resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.resourceDocumentActionObservable(resource, document, SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_REVIEW_REJECT); + } + + /** + * Method returns observable for review reject of the document for resource on the server. + * @param resource resource for which document belongs to. + * @param document document to be rejected. + */ + public reviewRejectSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + return this.subresourceDocumentActionObservable(subresource, resource, document, SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_REJECT); + } + + /** + * 'Method returns http-post observable for document requests for given url template address. The template should have properties + * user-id and resource-id which are replaced with current user id and resource id. The document is sent as payload. + * @param resource resource for which document belongs to. + * @param document document to be sent. + * @param reviewUrlTemplate url template for document action. + * @returns observable of DocumentRo + */ + public resourceDocumentActionObservable(resource: ResourceRo, document: DocumentRo, reviewUrlTemplate: string): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.get<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + return this.http.post<DocumentRo>(reviewUrlTemplate .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) - .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), {params}); + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), document); } - public saveSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + /** + * 'Method returns http-post observable for document requests for given url template address. The template should have properties + * user-id, resource-id and subresource-id. which are replaced with current user id, resource id and subresource-id. The document is sent as payload. + * @param subresource subresource for which document belongs to. + * @param resource resource for which document belongs to. + * @param document document to be sent. + * @param reviewUrlTemplate url template for document action. + * @returns observable of DocumentRo + */ + public subresourceDocumentActionObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo, reviewUrlTemplate: string): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.put<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + return this.http.post<DocumentRo>(reviewUrlTemplate .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), document); } - public validateSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo, document: DocumentRo): Observable<DocumentRo> { + /** + * 'Method returns http-post observable to generate of new payload for resource document. + * @param resource resource for which document belongs to. + * @returns observable of DocumentRo + */ + public generateResourceDocumentObservable(resource: ResourceRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); - return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE + return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_RESOURCE_GENERATE .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) - .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) - .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), document); + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), null); } + /** + * 'Method returns http-post observable to generate of new payload for subresource document. + * @param subresource subresource for which document belongs to. + * @param resource resource for which document belongs to. + * @returns observable of DocumentRo + */ public generateSubresourceDocumentObservable(subresource: SubresourceRo, resource: ResourceRo): Observable<DocumentRo> { const currentUser: User = this.securityService.getCurrentUser(); return this.http.post<DocumentRo>(SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE_GENERATE @@ -183,4 +324,27 @@ export class EditResourceService { .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subresource?.subresourceId), null); } + + getSubResourcesForResource(resource: ResourceRo): Observable<SubresourceRo[]> { + const currentUser: User = this.securityService.getCurrentUser(); + return this.http.get<SubresourceRo[]>(SmpConstants.REST_EDIT_SUBRESOURCE + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId)); + } + + deleteSubresourceFromResource(subResource: SubresourceRo, resource: ResourceRo): Observable<SubresourceRo> { + const currentUser: User = this.securityService.getCurrentUser(); + return this.http.delete<ResourceRo>(SmpConstants.REST_EDIT_SUBRESOURCE_DELETE + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId) + .replace(SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID, subResource?.subresourceId)); + } + + createSubResourceForResource(subresource: SubresourceRo, resource: ResourceRo): Observable<SubresourceRo> { + const currentUser: User = this.securityService.getCurrentUser(); + return this.http.put<SubresourceRo>(SmpConstants.REST_EDIT_SUBRESOURCE_CREATE + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, currentUser.userId) + .replace(SmpConstants.PATH_PARAM_ENC_RESOURCE_ID, resource?.resourceId), + subresource); + } } diff --git a/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.html b/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.html index a20246c05a800c9a3393e9e090f23736328de828..fe07b3bbaf6f3cd3c246f2637f779d53c0c98419 100644 --- a/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.html +++ b/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.html @@ -27,31 +27,49 @@ </option> </select> </mat-form-field> - <mat-form-field style="width: 100%"> + <mat-form-field style="width: 100%"> <mat-label>{{ "resource.details.panel.label.resource.id" | translate }}</mat-label> <input id="identifierValue_id" type="text" matInput #identifierValue formControlName="identifierValue" required auto-focus-directive> </mat-form-field> - <mat-form-field style="width: 100%"> + <mat-form-field style="width: 100%"> <mat-label>{{ "resource.details.panel.label.resource.scheme" | translate }}</mat-label> <input id="identifierScheme_id" type="text" matInput formControlName="identifierScheme" > </mat-form-field> + <mat-checkbox formControlName="reviewEnabled" + matTooltip="{{ 'resource.details.panel.label.resource.review.enabled' | translate }}" + id="reviewEnabled_id"> + {{ "resource.details.panel.tooltip.resource.review.enabled" | translate }} + </mat-checkbox> <mat-form-field style="width:100%"> <mat-label>{{ "resource.details.panel.label.resource.visibility" | translate }}</mat-label> <select matNativeControl formControlName="visibility" - matTooltip="{{ 'resource.details.panel.tooltip.resource.visibility' | translate }}" - id="visibility_id" required> + matTooltip="{{ 'resource.details.panel.tooltip.resource.visibility' | translate }}" + id="visibility_id" required> <option *ngFor="let visibility of groupVisibilityOptions" - [value]="visibility.value"> - {{visibility.key}} + [value]="visibility.value"> + {{ visibility.key }} </option> </select> </mat-form-field> - + <mat-toolbar class ="mat-elevation-z2"> + <mat-toolbar-row class="smp-toolbar-row"> + <button id="cancelButton" mat-raised-button (click)="onResetButtonClicked()" color="primary" + [disabled]="!resetButtonEnabled"> + <mat-icon>refresh</mat-icon> + <span>{{ "resource.details.panel.button.reset" | translate }}</span> + </button> + <button id="saveButton" mat-raised-button (click)="onSaveButtonClicked()" color="primary" + [disabled]="!submitButtonEnabled"> + <mat-icon>save</mat-icon> + <span>{{ "resource.details.panel.button.save" | translate }}</span> + </button> + </mat-toolbar-row> + </mat-toolbar> <smp-warning-panel type="desc" icon="info" diff --git a/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.ts b/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.ts index 4ed1a074b5825238788660a2faa3c77e796d2e4f..0329de97e2d1b94e2fd4e178365c61d26f24ecee 100644 --- a/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.ts +++ b/smp-angular/src/app/edit/edit-resources/resource-details-panel/resource-details-panel.component.ts @@ -1,17 +1,30 @@ import {Component, Input,} from '@angular/core'; import {MatDialog} from "@angular/material/dialog"; -import {BeforeLeaveGuard} from "../../../window/sidenav/navigation-on-leave-guard"; +import { + BeforeLeaveGuard +} from "../../../window/sidenav/navigation-on-leave-guard"; import {GroupRo} from "../../../common/model/group-ro.model"; import {ResourceRo} from "../../../common/model/resource-ro.model"; -import {AlertMessageService} from "../../../common/alert-message/alert-message.service"; +import { + AlertMessageService +} from "../../../common/alert-message/alert-message.service"; import {DomainRo} from "../../../common/model/domain-ro.model"; -import {ResourceDefinitionRo} from "../../../system-settings/admin-extension/resource-definition-ro.model"; +import { + ResourceDefinitionRo +} from "../../../system-settings/admin-extension/resource-definition-ro.model"; import {EditResourceService} from "../edit-resource.service"; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {VisibilityEnum} from "../../../common/enums/visibility.enum"; -import {NavigationNode, NavigationService} from "../../../window/sidenav/navigation-model.service"; +import { + NavigationNode, + NavigationService +} from "../../../window/sidenav/navigation-model.service"; import {TranslateService} from "@ngx-translate/core"; import {lastValueFrom} from "rxjs"; +import { + WindowSpinnerService +} from "../../../common/services/window-spinner.service"; +import {EditResourceController} from "../edit-resource.controller"; @Component({ @@ -37,8 +50,10 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { constructor(private editResourceService: EditResourceService, + private editResourceController: EditResourceController, private navigationService: NavigationService, private alertService: AlertMessageService, + private windowSpinnerService: WindowSpinnerService, private dialog: MatDialog, private formBuilder: FormBuilder, private translateService: TranslateService) { @@ -47,6 +62,7 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { 'identifierScheme': new FormControl({value: null}), 'visibility': new FormControl({value: null}), 'resourceTypeIdentifier': new FormControl({value: null}), + 'reviewEnabled': new FormControl({value: null}), '': new FormControl({value: null}) }); this.translateService.get("resource.details.panel.title").subscribe(value => this.title = value); @@ -59,6 +75,7 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { resource.identifierValue = this.resourceForm.get('identifierValue').value; resource.resourceTypeIdentifier = this.resourceForm.get('resourceTypeIdentifier').value; resource.visibility = this.resourceForm.get('visibility').value; + resource.reviewEnabled = this.resourceForm.get('reviewEnabled').value; return resource; } @@ -70,12 +87,17 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { this.resourceForm.controls['identifierScheme'].setValue(value.identifierScheme); this.resourceForm.controls['resourceTypeIdentifier'].setValue(value.resourceTypeIdentifier); this.resourceForm.controls['visibility'].setValue(value.visibility); + this.resourceForm.controls['reviewEnabled'].setValue(value.reviewEnabled); + // only allow visibility and reviewEnabled changes for group-admin and resource-admin + this.resourceForm.controls['visibility'].enable(); + this.resourceForm.controls['reviewEnabled'].enable(); } else { this.resourceForm.controls['identifierValue'].setValue(""); this.resourceForm.controls['identifierScheme'].setValue(""); this.resourceForm.controls['resourceTypeIdentifier'].setValue(""); this.resourceForm.controls['visibility'].setValue(""); + this.resourceForm.controls['reviewEnabled'].setValue(false); } (async () => await this.updateVisibilityDescription())(); this.resourceForm.markAsPristine(); @@ -85,12 +107,12 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { // set selected resource this.editResourceService.selectedResource = this.resource; - let node: NavigationNode = await this.createNew(); + let node: NavigationNode = await this.createNewDocumentNavigationNode(); this.navigationService.selected.children = [node] this.navigationService.select(node); } - public async createNew() { + public async createNewDocumentNavigationNode() { return { code: "resource-document", icon: "note", @@ -110,12 +132,46 @@ export class ResourceDetailsPanelComponent implements BeforeLeaveGuard { async updateVisibilityDescription() { if (this.resourceForm.get('visibility').value == VisibilityEnum.Private) { this.visibilityDescription = await lastValueFrom(this.translateService.get("resource.details.panel.label.resource.visibility.private")); - } else if (this.group.visibility == VisibilityEnum.Private) { + } else if (this.group?.visibility == VisibilityEnum.Private) { this.visibilityDescription = await lastValueFrom(this.translateService.get("resource.details.panel.label.resource.visibility.private.group")); - } else if (this.domain.visibility == VisibilityEnum.Private) { + } else if (this.domain?.visibility == VisibilityEnum.Private) { this.visibilityDescription = await lastValueFrom(this.translateService.get("resource.details.panel.label.resource.visibility.private.domain")); } else { this.visibilityDescription = await lastValueFrom(this.translateService.get("resource.details.panel.label.resource.visibility.public")); } } + + get submitButtonEnabled(): boolean { + return this.resourceForm.valid && this.resourceForm.dirty; + } + + get resetButtonEnabled(): boolean { + return this.resourceForm.dirty; + } + + public onSaveButtonClicked(): void { + this.windowSpinnerService.showSpinner = true; + let updatedResource: ResourceRo = this.resource; + this.editResourceService.updateResourceForGroup(updatedResource, this.group, this.domain).subscribe({ + next: (result: ResourceRo): void => { + try { + if (!!result) { + this.alertService.successForTranslation("resource.details.panel.alert.resource.saved"); + this.editResourceController.selectedResource = result; + this._resource = result; + this.resourceForm.markAsPristine(); + } + } finally { + this.windowSpinnerService.showSpinner = false; + } + }, error: (err: any): void => { + this.alertService.error(err.error?.errorDescription) + this.windowSpinnerService.showSpinner = false; + } + }); + } + + public onResetButtonClicked() { + this.resourceForm.reset(this._resource); + } } diff --git a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.html b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.html index d63c1ba96d581cea00734bb2a2d5c1faec74c0ac..0cbbc33f12568a01aee20c8079e6e5b8d790e54e 100644 --- a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.html +++ b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.html @@ -1,109 +1,3 @@ -<div id="resource-document-panel"> - - <mat-toolbar class="mat-elevation-z2" style="min-height: 50px !important;"> - <mat-toolbar-row class="smp-toolbar-row" - style="justify-content: space-between;min-height: 50px !important;"> - - <button id="validateResource_id" mat-raised-button - color="primary" - matTooltip="{{ 'resource.document.panel.tooltip.validate' | translate }}" - [disabled]="emptyDocument" - (click)="onDocumentValidateButtonClicked()" - > - <mat-icon>check_circle</mat-icon> - <span>{{ "resource.document.panel.button.validate" | translate }}</span> - </button> - <button id="GenerateResource_id" mat-raised-button - color="primary" - matTooltip="{{ 'resource.document.panel.tooltip.generate' | translate }}" - - (click)="onGenerateButtonClicked()" - > - <mat-icon>add_circle</mat-icon> - <span>{{ "resource.document.panel.button.generate" | translate }}</span> - </button> - <button id="documentWizard_id" mat-raised-button - color="primary" - matTooltip="{{ 'resource.document.panel.tooltip.document.wizard' | translate }}" - *ngIf="showWizardDialog" - (click)="onShowDocumentWizardDialog()" - > - <mat-icon>code_block</mat-icon> - <span>{{ "resource.document.panel.button.document.wizard" | translate }}</span> - </button> - <span style="flex: 1 1 auto;"></span> - - <div [formGroup]="documentForm" - style="float: right; vertical-align:middle;display: flex;align-items: center;justify-content: center; gap:0.4em;padding-right: 10px"> - <span style="font-size: 0.8em">{{ "resource.document.panel.label.show.version" | translate }}</span> - <select matNativeControl - style="width: 100px; border-bottom: grey solid 1px" - placeholder="{{ 'resource.document.panel.placeholder.version' | translate }}" - matTooltip="{{ 'resource.document.panel.tooltip.version' | translate }}" - formControlName="payloadVersion" - id="document version_id" - (change)="onSelectionDocumentVersionChanged()" - > - <option *ngFor="let version of getDocumentVersions" - [value]="version" - > - {{ version }} - </option> - </select> - - <span style="font-size: 0.8em">{{ "resource.document.panel.label.created.on" | translate }}</span> - <input id="payloadCreatedOn_id" - matInput [ngxMatDatetimePicker]="payloadCreatedOnPicker" - formControlName="payloadCreatedOn" - readonly> - <mat-datepicker-toggle matSuffix [for]="payloadCreatedOnPicker" - style="visibility: hidden"></mat-datepicker-toggle> - <ngx-mat-datetime-picker #payloadCreatedOnPicker [showSpinners]="true" - [showSeconds]="false" - [hideTime]="false"></ngx-mat-datetime-picker> - - </div> - </mat-toolbar-row> - </mat-toolbar> - - <div class="panel" [formGroup]="documentForm" - style="display: flex;flex-direction: row;flex-grow: 1; "> - - <div - style="display:block; overflow: auto;flex: 2;align-self: stretch; flex-direction: column;border: ridge 3px #b0bec5" - (click)="onEditPanelClick()" - > - <smp-editor #smpDocumentEditor formControlName="payload" - ngDefaultControl></smp-editor> - </div> - <document-properties-panel style="min-height: 0; overflow: auto" - formControlName="properties" - ngDefaultControl></document-properties-panel> - </div> - - <mat-toolbar class="mat-elevation-z2" - style="flex-grow: 0;"> - <mat-toolbar-row class="smp-toolbar-row"> - <button id="back_id" mat-raised-button color="primary" - (click)="onBackButtonClicked()"> - <mat-icon>arrow_circle_left</mat-icon> - <span>{{ "resource.document.panel.button.back" | translate }}</span> - </button> - <button id="cancel_id" mat-raised-button color="primary" - [disabled]="cancelButtonDisabled" - (click)="onDocumentResetButtonClicked()"> - <mat-icon>cancel</mat-icon> - <span>{{ "resource.document.panel.button.cancel" | translate }}</span> - </button> - <button id="saveResource_id" mat-raised-button - color="primary" - matTooltip="{{ 'resource.document.panel.tooltip.save' | translate }}" - [disabled]="saveButtonDisabled" - (click)="onSaveButtonClicked()" - > - <mat-icon>save</mat-icon> - <span>{{ "resource.document.panel.button.save" | translate }}</span> - </button> - </mat-toolbar-row> - </mat-toolbar> -</div> +<document-edit-panel #documentEditor + editorMode="RESOURCE_EDITOR"> +</document-edit-panel> diff --git a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.scss b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.scss index 629dfc616c3572d572df60f2224542a1870ebfa5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.scss +++ b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.scss @@ -1,10 +0,0 @@ -#resource-document-panel { - display: flex; - height: 100%; - flex-direction: column; - -} - -.CodeMirror { - height: auto; -} diff --git a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.ts b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.ts index 49ee1748702995f381a54b7204be0f5aaa5ea4ed..cece275aa7924ee5122c96cc6ddc023edbb0b9ee 100644 --- a/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.ts +++ b/smp-angular/src/app/edit/edit-resources/resource-document-panel/resource-document-panel.component.ts @@ -1,35 +1,10 @@ -import {Component, Input, ViewChild, ViewEncapsulation,} from '@angular/core'; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import {Component, ViewChild, ViewEncapsulation,} from '@angular/core'; import { BeforeLeaveGuard } from "../../../window/sidenav/navigation-on-leave-guard"; -import {GroupRo} from "../../../common/model/group-ro.model"; -import {ResourceRo} from "../../../common/model/resource-ro.model"; import { - AlertMessageService -} from "../../../common/alert-message/alert-message.service"; -import {DomainRo} from "../../../common/model/domain-ro.model"; -import { - ResourceDefinitionRo -} from "../../../system-settings/admin-extension/resource-definition-ro.model"; -import {EditResourceService} from "../edit-resource.service"; -import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; -import {DocumentRo} from "../../../common/model/document-ro.model"; -import { - NavigationService -} from "../../../window/sidenav/navigation-model.service"; -import { - DocumentWizardDialogComponent -} from "../document-wizard-dialog/document-wizard-dialog.component"; -import { - ConfirmationDialogComponent -} from "../../../common/dialogs/confirmation-dialog/confirmation-dialog.component"; -import { - SmpEditorComponent -} from "../../../common/components/smp-editor/smp-editor.component"; -import {EntityStatus} from "../../../common/enums/entity-status.enum"; -import {TranslateService} from "@ngx-translate/core"; -import {lastValueFrom} from "rxjs"; + DocumentEditPanelComponent +} from "../../../common/panels/document-edit-panel/document-edit-panel.component"; @Component({ templateUrl: './resource-document-panel.component.html', @@ -37,255 +12,15 @@ import {lastValueFrom} from "rxjs"; encapsulation: ViewEncapsulation.None, }) export class ResourceDocumentPanelComponent implements BeforeLeaveGuard { - private _resource: ResourceRo; - - _document: DocumentRo; - @Input() private group: GroupRo; - @Input() domain: DomainRo; - @Input() domainResourceDefs: ResourceDefinitionRo[]; - - @ViewChild("smpDocumentEditor") documentEditor: SmpEditorComponent; - - - resourceForm: FormGroup; - documentForm: FormGroup; - - constructor(private editResourceService: EditResourceService, - private alertService: AlertMessageService, - private dialog: MatDialog, - private navigationService: NavigationService, - private formBuilder: FormBuilder, - private translateService: TranslateService) { - this.resourceForm = formBuilder.group({ - 'identifierValue': new FormControl({value: null}), - 'identifierScheme': new FormControl({value: null}), - 'visibility': new FormControl({value: null}), - 'resourceTypeIdentifier': new FormControl({value: null}), - }); - this.documentForm = formBuilder.group({ - 'mimeType': new FormControl({value: null}), - 'name': new FormControl({value: null}), - 'currentResourceVersion': new FormControl({value: null}), - 'payloadCreatedOn': new FormControl({value: null}), - 'payloadVersion': new FormControl({value: null}), - 'payload': new FormControl({value: null}), - 'properties': new FormControl({value: null}), - }); - this.resource = editResourceService.selectedResource - - this.documentForm.controls['payload'].setValue("") - } - - get resource(): ResourceRo { - let resource = {...this._resource}; - resource.identifierScheme = this.resourceForm.get('identifierScheme').value; - resource.identifierValue = this.resourceForm.get('identifierValue').value; - resource.resourceTypeIdentifier = this.resourceForm.get('resourceTypeIdentifier').value; - resource.visibility = this.resourceForm.get('visibility').value; - return resource; - } - - @Input() set resource(value: ResourceRo) { - this._resource = value; - if (!this._resource) { - this.navigationService.navigateToHome(); - return; - } - - this.resourceForm.enable(); - this.resourceForm.controls['identifierValue'].setValue(value.identifierValue); - this.resourceForm.controls['identifierScheme'].setValue(value.identifierScheme); - this.resourceForm.controls['resourceTypeIdentifier'].setValue(value.resourceTypeIdentifier); - this.resourceForm.controls['visibility'].setValue(value.visibility); - // control disable enable did not work?? - - this.resourceForm.controls['identifierValue'].disable(); - this.resourceForm.controls['identifierScheme'].disable(); - this.resourceForm.controls['resourceTypeIdentifier'].disable(); - this.resourceForm.controls['visibility'].disable(); - this.resourceForm.markAsPristine(); - // load current document for the resource - this.loadDocumentForVersion(); - } - - @Input() set document(value: DocumentRo) { - this._document = value; - this.documentForm.disable(); - if (!!value) { - this.documentEditor.mimeType = value.mimeType; - this.documentForm.controls['mimeType'].setValue(value.mimeType); - this.documentForm.controls['name'].setValue(value.name); - this.documentForm.controls['currentResourceVersion'].setValue(value.currentResourceVersion); - this.documentForm.controls['payloadVersion'].setValue(value.payloadVersion); - this.documentForm.controls['payloadCreatedOn'].setValue(value.payloadCreatedOn); - this.documentForm.controls['payload'].setValue(value.payload); - this.documentForm.controls['payload'].enable(); - this.documentForm.controls['properties'].setValue(value.properties); - // the method documentVersionsExists already uses the current value to check if versions exists - if (!this.documentVersionsExists) { - this.documentForm.controls['payloadVersion'].disable(); - } else { - this.documentForm.controls['payloadVersion'].enable(); - } - } else { - this.documentForm.controls['name'].setValue(""); - this.documentForm.controls['payload'].setValue(""); - this.documentForm.controls['currentResourceVersion'].setValue(""); - this.documentForm.controls['payloadVersion'].setValue(""); - this.documentForm.controls['payloadCreatedOn'].setValue(""); - this.documentForm.controls['payload'].setValue(""); - this.documentForm.controls['properties'].setValue([]); - } - this.documentForm.markAsPristine(); - } - - get document(): DocumentRo { - let doc: DocumentRo = {...this._document}; - if(this.documentForm.controls['payload'].dirty){ - doc.payload = this.documentForm.controls['payload'].value; - doc.payloadStatus = EntityStatus.UPDATED; - } - doc.properties = this.documentForm.controls['properties'].value; - return doc; - } - - onBackButtonClicked(): void { - this.navigationService.navigateUp(); - } - - async onDocumentResetButtonClicked() { - this.dialog.open(ConfirmationDialogComponent, { - data: { - title: await lastValueFrom(this.translateService.get("resource.document.panel.cancel.confirmation.dialog.title")), - description: await lastValueFrom(this.translateService.get("resource.document.panel.cancel.confirmation.dialog.description")) - } - }).afterClosed().subscribe(result => { - if (result) { - this.resetChanges() - } - }); - } - - resetChanges() { - - let currentVersion = this._document?.payloadVersion; - if (!currentVersion) { - this.documentForm.controls['payload'].setValue(""); - this.documentForm.markAsPristine(); - } else { - this.loadDocumentForVersion(currentVersion); - } - } + @ViewChild('documentEditor') documentEditor: DocumentEditPanelComponent; - onSaveButtonClicked(): void { + constructor() { - this.editResourceService.saveDocumentObservable(this._resource, this.document).subscribe(async (value: DocumentRo) => { - if (value) { - this.alertService.success(await lastValueFrom(this.translateService.get("resource.document.panel.success.save", {currentResourceVersion: value.currentResourceVersion}))); - this.document = value; - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }) - } - - onGenerateButtonClicked(): void { - this.editResourceService.generateDocumentObservable(this._resource).subscribe(async (value: DocumentRo) => { - if (value) { - this.alertService.success(await lastValueFrom(this.translateService.get("resource.document.panel.success.generate"))) - this.documentForm.controls['payload'].setValue(value.payload); - this.documentForm.controls['payload'].markAsDirty(); - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }) - } - - async onShowDocumentWizardDialog() { - - const formRef: MatDialogRef<any> = this.dialog.open(DocumentWizardDialogComponent, { - data: { - title: await lastValueFrom(this.translateService.get("resource.document.panel.document.wizard.dialog.title")), - resource: this._resource, - - } - }); - formRef.afterClosed().subscribe(result => { - if (result) { - let val = formRef.componentInstance.getExtensionXML(); - this.documentForm.controls['payload'].setValue(val); - this.documentForm.controls['payload'].markAsDirty(); - } - }); - } - - loadDocumentForVersion(version: number = null): void { - this.editResourceService.getDocumentObservable(this._resource, version).subscribe((value: DocumentRo) => { - if (value) { - this.document = value; - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }); - } - - validateCurrentDocument(): void { - this.editResourceService.validateDocumentObservable(this._resource, this.document).subscribe(async (value: DocumentRo) => { - this.alertService.success(await lastValueFrom(this.translateService.get("resource.document.panel.success.valid"))) - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }); - } - - onDocumentValidateButtonClicked(): void { - this.validateCurrentDocument(); - } - - onSelectionDocumentVersionChanged(): void { - this.loadDocumentForVersion(this.documentForm.controls['payloadVersion'].value) - } - - public onEditPanelClick() { - - if (this.documentEditor.hasFocus) { - return; - } - this.documentEditor.focusAndCursorToEnd(); - } - - get getDocumentVersions(): number[] { - return !this._document?.allVersions ? [] : this._document?.allVersions; - } - - get emptyDocument(): boolean { - return !this.documentForm.controls['payload']?.value - } - - get documentVersionsExists(): boolean { - return this.getDocumentVersions.length > 0 - } - - get cancelButtonDisabled(): boolean { - return !this.documentForm.dirty; - } - - get saveButtonDisabled(): boolean { - return !this.documentForm.dirty || !this.documentForm.controls['payload']?.value; } isDirty(): boolean { - return this.documentForm.dirty + return this.documentEditor.isDirty(); } - get showWizardDialog(): boolean { - // in version DomiSMP 5.0 CR show only the wizard for edelivery-oasis-smp-1.0-servicegroup - return this._resource?.resourceTypeIdentifier === 'edelivery-oasis-smp-1.0-servicegroup'; - } } diff --git a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.html b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.html index a2c14f775dced76a1f840d2cb755de1bdcc2f2d5..87ea7817e91f2df51a4847ebb51dfa368b1ed20d 100644 --- a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.html +++ b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.html @@ -1,101 +1,3 @@ -<div id="resource-document-panel"> - - <mat-toolbar class="mat-elevation-z2" style="min-height: 50px !important;"> - <mat-toolbar-row class="smp-toolbar-row" style="justify-content: space-between;min-height: 50px !important;"> - <button id="validateResource_id" mat-raised-button - color="primary" - [disabled]="emptyDocument" - matTooltip="{{ 'subresource.document.panel.tooltip.validate' | translate }}" - (click)="onDocumentValidateButtonClicked()" - > - <mat-icon>check_circle</mat-icon> - <span>{{ "subresource.document.panel.button.validate" | translate }}</span> - </button> - <button id="GenerateResource_id" mat-raised-button - color="primary" - matTooltip="{{ 'subresource.document.panel.tooltip.generate' | translate }}" - (click)="onGenerateButtonClicked()" - > - <mat-icon>add_circle</mat-icon> - <span>{{ "subresource.document.panel.button.generate" | translate }}</span> - </button> - <button id="documentWizard_id" mat-raised-button - color="primary" - matTooltip="{{ 'subresource.document.panel.tooltip.document.wizard' | translate }}" - *ngIf="showWizardDialog" - (click)="onShowDocumentWizardDialog()" - > - <mat-icon>code_block</mat-icon> - <span>{{ "subresource.document.panel.button.document.wizard" | translate }}</span> - </button> - <span style="flex: 1 1 auto;"></span> - - <div [formGroup]="documentForm" - style="float: right; vertical-align:middle;display: flex;align-items: center;justify-content: center; gap:0.4em;padding-right: 10px"> - <span style="font-size: 0.8em">{{ "subresource.document.panel.label.show.version" | translate }}</span> - <select matNativeControl style="width: 100px; border-bottom: grey solid 1px" - placeholder="{{ 'subresource.document.panel.placeholder.show.version' | translate }}" - matTooltip="{{ 'subresource.document.panel.tooltip.show.version' | translate }}" - formControlName="payloadVersion" - id="document version_id" - (change)="onSelectionDocumentVersionChanged()" - > - <option *ngFor="let version of getDocumentVersions" - [value]="version" - > - {{version}} - </option> - </select> - <span style="font-size: 0.8em">{{ "subresource.document.panel.label.created.on" | translate }}</span> - <input id="payloadCreatedOn_id" - matInput [ngxMatDatetimePicker]="payloadCreatedOnPicker" - formControlName="payloadCreatedOn" - readonly> - <mat-datepicker-toggle matSuffix [for]="payloadCreatedOnPicker" - style="visibility: hidden"></mat-datepicker-toggle> - <ngx-mat-datetime-picker #payloadCreatedOnPicker [showSpinners]="true" [showSeconds]="false" - [hideTime]="false"></ngx-mat-datetime-picker> - </div> - </mat-toolbar-row> - </mat-toolbar> - <div class="panel" [formGroup]="documentForm" - style="display: flex;flex-direction: row;flex-grow: 1; "> - - <div - style="display:block; overflow: auto;flex: 2;align-self: stretch; flex-direction: column;border: ridge 3px #b0bec5" - (click)="onEditPanelClick()" - > - <smp-editor #smpDocumentEditor formControlName="payload" - ngDefaultControl></smp-editor> - </div> - <document-properties-panel style="min-height: 0; overflow: auto" - formControlName="properties" - ngDefaultControl></document-properties-panel> - </div> - - <mat-toolbar class="mat-elevation-z2" - style="flex-grow: 0;"> - <mat-toolbar-row class="smp-toolbar-row"> - <button id="back_id" mat-raised-button color="primary" - (click)="onBackButtonClicked()"> - <mat-icon>arrow_circle_left</mat-icon> - <span>{{ "subresource.document.panel.button.back" | translate }}</span> - </button> - <button id="cancel_id" mat-raised-button color="primary" - [disabled]="cancelButtonDisabled" - (click)="onDocumentResetButtonClicked()"> - <mat-icon>cancel</mat-icon> - <span>{{ "subresource.document.panel.button.cancel" | translate }}</span> - </button> - <button id="saveResource_id" mat-raised-button - color="primary" - matTooltip="{{ 'subresource.document.panel.tooltip.save' | translate }}" - [disabled]="saveButtonDisabled" - (click)="onSaveButtonClicked()" - > - <mat-icon>save</mat-icon> - <span>{{ "subresource.document.panel.button.save" | translate }}</span> - </button> - </mat-toolbar-row> - </mat-toolbar> -</div> +<document-edit-panel #subresourceDocumentEditor + editorMode="SUBRESOURCE_EDITOR"> +</document-edit-panel> diff --git a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.scss b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.scss index b422b79b3fe74ad2d66417ea0e53f50bf4663229..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.scss +++ b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.scss @@ -1,10 +0,0 @@ -#resource-document-panel { - display: flex; - height: 100%; - flex-direction: column; - gap:0.5em; -} - -.CodeMirror { - height: auto; -} diff --git a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.ts b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.ts index 1cc9f421fdcddfd82946a7aa1e6b14a01fb4abbc..6f870813750aaa226aa2b05b3e40e761f07ce2be 100644 --- a/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.ts +++ b/smp-angular/src/app/edit/edit-resources/subresource-document-panel/subresource-document-panel.component.ts @@ -1,328 +1,26 @@ -import {AfterViewInit, Component, Input, ViewChild, ViewEncapsulation,} from '@angular/core'; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; -import {BeforeLeaveGuard} from "../../../window/sidenav/navigation-on-leave-guard"; -import {GroupRo} from "../../../common/model/group-ro.model"; -import {ResourceRo} from "../../../common/model/resource-ro.model"; -import {AlertMessageService} from "../../../common/alert-message/alert-message.service"; -import {DomainRo} from "../../../common/model/domain-ro.model"; -import {ResourceDefinitionRo} from "../../../system-settings/admin-extension/resource-definition-ro.model"; -import {EditResourceService} from "../edit-resource.service"; -import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; -import {DocumentRo} from "../../../common/model/document-ro.model"; -import {NavigationService} from "../../../window/sidenav/navigation-model.service"; -import {SubresourceRo} from "../../../common/model/subresource-ro.model"; +import {Component, ViewChild, ViewEncapsulation,} from '@angular/core'; import { - SubresourceDocumentWizardComponent -} from "../subresource-document-wizard-dialog/subresource-document-wizard.component"; + BeforeLeaveGuard +} from "../../../window/sidenav/navigation-on-leave-guard"; import { - SubresourceWizardRo -} from "../subresource-document-wizard-dialog/subresource-wizard-edit-ro.model"; -import {ConfirmationDialogComponent} from "../../../common/dialogs/confirmation-dialog/confirmation-dialog.component"; -import {SmpEditorComponent} from "../../../common/components/smp-editor/smp-editor.component"; -import {EntityStatus} from "../../../common/enums/entity-status.enum"; -import {TranslateService} from "@ngx-translate/core"; -import {lastValueFrom} from "rxjs"; + DocumentEditPanelComponent +} from "../../../common/panels/document-edit-panel/document-edit-panel.component"; @Component({ templateUrl: './subresource-document-panel.component.html', styleUrls: ['./subresource-document-panel.component.scss'], encapsulation: ViewEncapsulation.None, }) -export class SubresourceDocumentPanelComponent implements AfterViewInit, BeforeLeaveGuard { +export class SubresourceDocumentPanelComponent implements BeforeLeaveGuard { - private _resource: ResourceRo; - private _subresource: SubresourceRo; + @ViewChild('subresourceDocumentEditor') documentEditor: DocumentEditPanelComponent; - _document: DocumentRo; - @Input() private group: GroupRo; - @Input() domain: DomainRo; - @Input() domainResourceDefs: ResourceDefinitionRo[]; + constructor() { - @ViewChild("smpDocumentEditor") documentEditor: SmpEditorComponent; - - resourceForm: FormGroup; - subresourceForm: FormGroup; - documentForm: FormGroup; - - constructor(private editResourceService: EditResourceService, - private alertService: AlertMessageService, - private dialog: MatDialog, - private navigationService: NavigationService, - private formBuilder: FormBuilder, - private translateService: TranslateService) { - this.resourceForm = this.formBuilder.group({ - 'identifierValue': new FormControl({value: null}), - 'identifierScheme': new FormControl({value: null}), - 'visibility': new FormControl({value: null}), - 'resourceTypeIdentifier': new FormControl({value: null}), - }); - this.subresourceForm = this.formBuilder.group({ - 'identifierValue': new FormControl({value: null}), - 'identifierScheme': new FormControl({value: null}), - 'subresourceTypeIdentifier': new FormControl({value: null}), - }); - - this.documentForm = this.formBuilder.group({ - 'mimeType': new FormControl({value: null}), - 'name': new FormControl({value: null}), - 'currentResourceVersion': new FormControl({value: null}), - 'payloadVersion': new FormControl({value: null}), - 'payloadCreatedOn': new FormControl({value: null}), - 'payload': new FormControl({value: null}), - 'properties': new FormControl({value: null}), - }); - this.documentForm.controls['payload'].setValue("") - - this.resource = editResourceService.selectedResource - this.subresource = editResourceService.selectedSubresource - - - } - - ngAfterViewInit(): void { - // this.codemirror.codeMirror.setSize('100%', '100%'); - } - - get resource(): ResourceRo { - let resource = {...this._resource}; - resource.identifierScheme = this.resourceForm.get('identifierScheme').value; - resource.identifierValue = this.resourceForm.get('identifierValue').value; - resource.resourceTypeIdentifier = this.resourceForm.get('resourceTypeIdentifier').value; - resource.visibility = this.resourceForm.get('visibility').value; - return resource; - } - - @Input() set resource(value: ResourceRo) { - this._resource = value; - - if (!this._resource) { - this.navigationService.reset(); - return; - } - - this.resourceForm.disable(); - this.resourceForm.controls['identifierValue'].setValue(value.identifierValue); - this.resourceForm.controls['identifierScheme'].setValue(value.identifierScheme); - this.resourceForm.controls['resourceTypeIdentifier'].setValue(value.resourceTypeIdentifier); - this.resourceForm.controls['visibility'].setValue(value.visibility); - this.resourceForm.markAsPristine(); - } - - get subresource(): SubresourceRo { - let subresource = {...this._subresource}; - subresource.identifierScheme = this.subresourceForm.get('identifierScheme').value; - subresource.identifierValue = this.subresourceForm.get('identifierValue').value; - subresource.subresourceTypeIdentifier = this.subresourceForm.get('subresourceTypeIdentifier').value; - return subresource; - } - - @Input() set subresource(value: SubresourceRo) { - this._subresource = value; - - if (!this._subresource) { - this.navigationService.reset(); - return; - } - - - this.subresourceForm.disable(); - this.subresourceForm.controls['identifierValue'].setValue(value.identifierValue); - this.subresourceForm.controls['identifierScheme'].setValue(value.identifierScheme); - this.subresourceForm.controls['subresourceTypeIdentifier'].setValue(value.subresourceTypeIdentifier); - this.resourceForm.markAsPristine(); - // load current document for the resource - this.loadDocumentForVersion(); - } - - @Input() set document(value: DocumentRo) { - this._document = value; - this.documentForm.disable(); - if (!!value){ - this.documentEditor.mimeType = value.mimeType; - this.documentForm.controls['mimeType'].setValue(value.mimeType); - this.documentForm.controls['name'].setValue(value.name); - this.documentForm.controls['currentResourceVersion'].setValue(value.currentResourceVersion); - this.documentForm.controls['payloadVersion'].setValue(value.payloadVersion); - this.documentForm.controls['payloadCreatedOn'].setValue(value.payloadCreatedOn); - this.documentForm.controls['payload'].setValue(!value.payload ? "" : value.payload); - this.documentForm.controls['payload'].enable(); - this.documentForm.controls['properties'].setValue(value.properties); - - if (!this.documentVersionsExists) { - this.documentForm.controls['payloadVersion'].disable(); - } else { - this.documentForm.controls['payloadVersion'].enable(); - } - - } else { - this.documentForm.controls['name'].setValue(""); - this.documentForm.controls['payload'].setValue(""); - this.documentForm.controls['currentResourceVersion'].setValue(""); - this.documentForm.controls['payloadVersion'].setValue(""); - this.documentForm.controls['payloadCreatedOn'].setValue(""); - this.documentForm.controls['payload'].setValue(""); - this.documentForm.controls['properties'].setValue([]); - } - this.documentForm.markAsPristine(); - } - - get document(): DocumentRo { - let doc: DocumentRo = {...this._document}; - if(this.documentForm.controls['payload'].dirty){ - doc.payload = this.documentForm.controls['payload'].value; - doc.payloadStatus = EntityStatus.UPDATED; - } - doc.properties = this.documentForm.controls['properties'].value; - return doc; - } - - onBackButtonClicked(): void { - this.navigationService.navigateUp(); - } - - onSaveButtonClicked(): void { - this.editResourceService.saveSubresourceDocumentObservable(this.subresource, this._resource, this.document).subscribe(async (value: DocumentRo) => { - if (value) { - this.alertService.success(await lastValueFrom(this.translateService.get("subresource.document.panel.success.save", {currentResourceVersion: value.currentResourceVersion}))); - this.document = value; - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }) - } - - onGenerateButtonClicked(): void { - this.editResourceService.generateSubresourceDocumentObservable(this.subresource, this._resource).subscribe(async (value: DocumentRo) => { - if (value) { - this.alertService.success(await lastValueFrom(this.translateService.get("subresource.document.panel.success.generate"))) - this.documentForm.controls['payload'].setValue(value.payload); - this.documentForm.controls['payload'].markAsDirty(); - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }) - } - - onShowDocumentWizardDialog() { - - let serviceMetadataWizard: SubresourceWizardRo = { - isNewSubresource: false, - participantIdentifier: this._resource.identifierValue, - participantScheme: this._resource.identifierScheme, - documentIdentifier: this._subresource.identifierValue, - documentIdentifierScheme: this._subresource.identifierScheme, - processIdentifier: '', - processScheme: '', - transportProfile: 'bdxr-transport-ebms3-as4-v1p0', // default value for oasis AS4 - - endpointUrl: '', - endpointCertificate: '', - - serviceDescription: '', - technicalContactUrl: '', - - } - - const formRef: MatDialogRef<any> = this.dialog.open(SubresourceDocumentWizardComponent, { - data: serviceMetadataWizard - }); - formRef.afterClosed().subscribe(result => { - if (result) { - let smw: SubresourceWizardRo = formRef.componentInstance.getCurrent(); - this.documentForm.controls['payload'].setValue(smw.contentXML); - this.documentForm.controls['payload'].markAsDirty(); - } - }); - } - - loadDocumentForVersion(version: number = null): void { - this.editResourceService.getSubresourceDocumentObservable(this._subresource, this._resource, version).subscribe((value: DocumentRo) => { - if (value) { - this.document = value; - } else { - this.document = null; - } - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }); - } - - validateCurrentDocument(): void { - this.editResourceService.validateSubresourceDocumentObservable(this.subresource, this._resource, this.document).subscribe(async (value: DocumentRo) => { - this.alertService.success(await lastValueFrom(this.translateService.get("subresource.document.panel.success.valid"))) - }, (error: any) => { - this.alertService.error(error.error?.errorDescription) - }); - } - - onDocumentValidateButtonClicked(): void { - this.validateCurrentDocument(); - } - - onSelectionDocumentVersionChanged(): void { - this.loadDocumentForVersion(this.documentForm.controls['payloadVersion'].value) - } - - public onEditPanelClick() { - if (this.documentEditor.hasFocus) { - return; - } - this.documentEditor.focusAndCursorToEnd(); - } - - get getDocumentVersions(): number[] { - return !this._document?.allVersions ? [] : this._document?.allVersions; - } - - get documentVersionsExists(): boolean { - return this.getDocumentVersions.length > 0 - } - - get emptyDocument(): boolean { - return !this.documentForm.controls['payload']?.value - } - - get cancelButtonDisabled(): boolean { - return !this.documentForm.dirty; - } - - get saveButtonDisabled(): boolean { - return !this.documentForm.dirty || !this.documentForm.controls['payload']?.value; } isDirty(): boolean { - return this.documentForm.dirty - } - - async onDocumentResetButtonClicked() { - this.dialog.open(ConfirmationDialogComponent, { - data: { - title: await lastValueFrom(this.translateService.get("subresource.document.panel.cancel.confirmation.dialog.title")), - description: await lastValueFrom(this.translateService.get("subresource.document.panel.cancel.confirmation.dialog.description")) - } - }).afterClosed().subscribe(result => { - if (result) { - this.resetChanges() - } - }); - } - - resetChanges() { - let currentVersion = this._document?.payloadVersion; - if (!currentVersion) { - this.documentForm.controls['payload'].setValue(""); - this.documentForm.markAsPristine(); - } else { - this.loadDocumentForVersion(currentVersion); - } + return this.documentEditor.isDirty(); } - get showWizardDialog(): boolean { - // in version DomiSMP 5.0 CR show only the wizard for edelivery-oasis-smp-1.0-servicemetadata - return this._subresource?.subresourceTypeIdentifier === 'edelivery-oasis-smp-1.0-servicemetadata'; - } } diff --git a/smp-angular/src/app/edit/review-task/review-tasks.component.css b/smp-angular/src/app/edit/review-task/review-tasks.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/smp-angular/src/app/edit/review-task/review-tasks.component.html b/smp-angular/src/app/edit/review-task/review-tasks.component.html new file mode 100644 index 0000000000000000000000000000000000000000..bc3956fafde408e71843cdd19ed7620a3c901bc8 --- /dev/null +++ b/smp-angular/src/app/edit/review-task/review-tasks.component.html @@ -0,0 +1 @@ +<review-tasks-panel id="review-tasks-panel_id" [baseUrl]="reviewTaskUrl"></review-tasks-panel> diff --git a/smp-angular/src/app/edit/review-task/review-tasks.component.ts b/smp-angular/src/app/edit/review-task/review-tasks.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..30319707f825e4d7e43dfee06aee32d6c8041db2 --- /dev/null +++ b/smp-angular/src/app/edit/review-task/review-tasks.component.ts @@ -0,0 +1,20 @@ +import {Component} from '@angular/core'; +import {SmpConstants} from "../../smp.constants"; +import {SecurityService} from "../../security/security.service"; + +@Component({ + selector: 'smp-review-tasks', + templateUrl: './review-tasks.component.html', + styleUrls: ['./review-tasks.component.css'] +}) +export class ReviewTasksComponent { + + constructor(private securityService: SecurityService) { + } + + get reviewTaskUrl(): string { + return SmpConstants.REST_EDIT_REVIEW_TASK + .replace(SmpConstants.PATH_PARAM_ENC_USER_ID, this.securityService.getCurrentUser()?.userId); + } + +} diff --git a/smp-angular/src/app/guards/activate-child-document.guard.ts b/smp-angular/src/app/guards/activate-child-document.guard.ts index c244d19497c3eeb00702807f818c517a37ed2137..f1eea88bc4adcd2d0e359c293fed11353639cfc2 100644 --- a/smp-angular/src/app/guards/activate-child-document.guard.ts +++ b/smp-angular/src/app/guards/activate-child-document.guard.ts @@ -1,18 +1,23 @@ import {inject} from '@angular/core'; -import {SecurityService} from '../security/security.service'; -import {AlertMessageService} from "../common/alert-message/alert-message.service"; -import {Authority} from "../security/authority.model"; -import {ActivatedRouteSnapshot, CanActivateChildFn, CanActivateFn, RouterStateSnapshot} from "@angular/router"; -import {EditResourceService} from "../edit/edit-resources/edit-resource.service"; +import { + AlertMessageService +} from "../common/alert-message/alert-message.service"; +import { + ActivatedRouteSnapshot, + CanActivateFn, + RouterStateSnapshot +} from "@angular/router"; +import { + EditResourceService +} from "../edit/edit-resources/edit-resource.service"; export const activateChildResourceGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { - console.log("Is user is authorized"); const alertService: AlertMessageService = inject(AlertMessageService); const editResourceService: EditResourceService = inject(EditResourceService); - let resourceUndefined:boolean = !editResourceService.selectedResource; + let resourceUndefined: boolean = !editResourceService.selectedResource; if (resourceUndefined) { alertService.error('Resource/Subresource is not selected! Please select resource from resource edit panel.', true); } diff --git a/smp-angular/src/app/guards/activate-child-review.guard.ts b/smp-angular/src/app/guards/activate-child-review.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..39d67ffdafe557827fa6ba96e7b54e21a0c064b8 --- /dev/null +++ b/smp-angular/src/app/guards/activate-child-review.guard.ts @@ -0,0 +1,21 @@ +import {inject} from '@angular/core'; +import { + AlertMessageService +} from "../common/alert-message/alert-message.service"; +import { + ActivatedRouteSnapshot, + CanActivateFn, + RouterStateSnapshot +} from "@angular/router"; +import { + EditResourceService +} from "../edit/edit-resources/edit-resource.service"; + + +export const activateChildReviewGuard: CanActivateFn = + (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + const alertService: AlertMessageService = inject(AlertMessageService); + const editResourceService: EditResourceService = inject(EditResourceService); + + return true; + }; diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index 1581c54ebc485d3abf2ae5f48cb8008cb533efb8..8d8947ec3da841882c0c42840dc2a0ca19462e94 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -12,6 +12,10 @@ export class SmpConstants { public static readonly PATH_ACTION_CREATE: string = 'create'; public static readonly PATH_ACTION_GENERATE: string = 'generate'; + public static readonly PATH_ACTION_PUBLISH: string = 'publish'; + public static readonly PATH_ACTION_REVIEW_REQUEST: string = 'review-request'; + public static readonly PATH_ACTION_REVIEW_APPROVE: string = 'review-approve'; + public static readonly PATH_ACTION_REVIEW_REJECT: string = 'review-reject'; public static readonly PATH_ACTION_VALIDATE: string = 'validate'; public static readonly PATH_ACTION_PUT: string = 'put'; public static readonly PATH_ACTION_RETRIEVE: string = 'retrieve'; @@ -49,6 +53,7 @@ export class SmpConstants { public static readonly PATH_RESOURCE_TYPE_SUBRESOURCE: string = 'subresource'; public static readonly PATH_RESOURCE_TYPE_DOCUMENT: string = 'document'; public static readonly PATH_QUERY_FILTER_TYPE: string = 'type' + public static readonly PATH_RESOURCE_TYPE_REVIEW: string = 'review-task'; //------------------------------ @@ -60,20 +65,32 @@ export class SmpConstants { public static readonly REST_EDIT_RESOURCE_SHORT = SmpConstants.REST_EDIT + SmpConstants.PATH_RESOURCE_TYPE_RESOURCE + '/' + SmpConstants.PATH_PARAM_ENC_RESOURCE_ID; - public static readonly REST_EDIT_DOCUMENT = SmpConstants.REST_EDIT_RESOURCE_SHORT + '/' + SmpConstants.PATH_RESOURCE_TYPE_DOCUMENT; - public static readonly REST_EDIT_DOCUMENT_VALIDATE = SmpConstants.REST_EDIT_DOCUMENT + '/' + SmpConstants.PATH_ACTION_VALIDATE; - public static readonly REST_EDIT_DOCUMENT_GENERATE = SmpConstants.REST_EDIT_DOCUMENT + '/' + SmpConstants.PATH_ACTION_GENERATE; + public static readonly REST_EDIT_DOCUMENT_RESOURCE = SmpConstants.REST_EDIT_RESOURCE_SHORT + '/' + SmpConstants.PATH_RESOURCE_TYPE_DOCUMENT; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_VALIDATE = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_VALIDATE; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_GENERATE = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_GENERATE; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_PUBLISH = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_PUBLISH; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_REVIEW_REQUEST = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_REQUEST; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_REVIEW_APPROVE = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_APPROVE; + public static readonly REST_EDIT_DOCUMENT_RESOURCE_REVIEW_REJECT = SmpConstants.REST_EDIT_DOCUMENT_RESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_REJECT; + public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE = SmpConstants.REST_EDIT_RESOURCE_SHORT + '/' + SmpConstants.PATH_RESOURCE_TYPE_SUBRESOURCE + '/' + SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID + '/' + SmpConstants.PATH_RESOURCE_TYPE_DOCUMENT; - public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_VALIDATE; public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_GENERATE = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_GENERATE; + public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_PUBLISH = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_PUBLISH; + public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_REQUEST = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_REQUEST; + public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_APPROVE = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_APPROVE; + public static readonly REST_EDIT_DOCUMENT_SUBRESOURCE_REVIEW_REJECT = SmpConstants.REST_EDIT_DOCUMENT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_REVIEW_REJECT; public static readonly REST_EDIT_SUBRESOURCE = SmpConstants.REST_EDIT_RESOURCE_SHORT + '/' + SmpConstants.PATH_RESOURCE_TYPE_SUBRESOURCE; public static readonly REST_EDIT_SUBRESOURCE_DELETE = SmpConstants.REST_EDIT_SUBRESOURCE + '/' + SmpConstants.PATH_PARAM_ENC_SUBRESOURCE_ID + '/' + SmpConstants.PATH_ACTION_DELETE; public static readonly REST_EDIT_SUBRESOURCE_CREATE = SmpConstants.REST_EDIT_SUBRESOURCE + '/' + SmpConstants.PATH_ACTION_CREATE; + + public static readonly REST_EDIT_REVIEW_TASK = SmpConstants.REST_EDIT + SmpConstants.PATH_RESOURCE_TYPE_REVIEW + '/' + + /* Public services */ public static readonly REST_PUBLIC_SEARCH_RESOURCE = SmpConstants.REST_PUBLIC + SmpConstants.PATH_ACTION_SEARCH; public static readonly REST_PUBLIC_SEARCH_RESOURCE_METADATA = SmpConstants.REST_PUBLIC + SmpConstants.PATH_ACTION_SEARCH + "/metadata"; diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.html b/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.html index 534a31ba28b36e1924ec95410baf85e0eb5d3e7c..afd05698d7bd6cdfe390d3adb443a1dfd174849a 100644 --- a/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.html +++ b/smp-angular/src/app/system-settings/admin-domain/domain-panel/domain-panel.component.html @@ -1,13 +1,14 @@ -<div id="domain-panel" class="mat-elevation-z2" > - <form [formGroup]="domainForm" > +<div id="domain-panel" class="mat-elevation-z2"> + <form [formGroup]="domainForm"> <h3>{{ "domain.panel.title" | translate }}</h3> - <div class="panel" *ngIf="_domain!=null && !_domain.domainId"><p style="font-weight: bold">{{ "domain.panel.text" | translate }}</div> + <div class="panel" *ngIf="_domain!=null && !_domain.domainId"><p + style="font-weight: bold">{{ "domain.panel.text" | translate }}</div> <smp-warning-panel *ngIf="showWarning" icon="warning" type="warning" [htmlContent]="warningMessage"></smp-warning-panel> <mat-form-field style="width:100%"> - <mat-label>{{ "domain.panel.label.domain" | translate: { value: domainForm.controls['adminMemberCount'].value } }}</mat-label> + <mat-label>{{ "domain.panel.label.domain" | translate: {value: domainForm.controls['adminMemberCount'].value} }}</mat-label> <input matInput id="domainCode_id" #domainCode matTooltip="{{ 'domain.panel.tooltip.domain' | translate }}" @@ -15,7 +16,8 @@ (keydown)="onFieldKeyPressed('domainCode', 'domainCodeTimeout')" required auto-focus-directive> - <mat-hint align="end">{{ "domain.panel.hint.domain" | translate }}</mat-hint> + <mat-hint align="end">{{ "domain.panel.hint.domain" | translate }} + </mat-hint> <div *ngIf="(!editMode && domainForm.controls['domainCode'].touched || editMode) && domainForm.controls['domainCode'].hasError('pattern')" style="color:red; font-size: 70%"> @@ -37,51 +39,58 @@ <mat-select formControlName="signatureKeyAlias" matTooltip="{{ 'domain.panel.tooltip.signature.cert.alias' | translate }}" id="signatureKeyAlias_id"> - <mat-option [value]="''" ></mat-option> - <mat-option *ngFor="let cert of keystoreCertificates" [value]="cert.alias"> - {{cert.alias}} ({{cert.certificateId}}) + <mat-option [value]="''"></mat-option> + <mat-option *ngFor="let cert of keystoreCertificates" + [value]="cert.alias"> + {{ cert.alias }} ({{ cert.certificateId }}) </mat-option> </mat-select> - <mat-hint align="end">{{ "domain.panel.hint.signature.cert.alias" | translate }} + <mat-hint + align="end">{{ "domain.panel.hint.signature.cert.alias" | translate }} </mat-hint> </mat-form-field> <mat-form-field style="width:100%"> <mat-label>{{ "domain.panel.label.domain.visibility" | translate }}</mat-label> <select matNativeControl formControlName="visibility" - name="visibility" - matTooltip="{{ 'domain.panel.tooltip.domain.visibility' | translate }}" - id="domainVisibility_id"> + name="visibility" + matTooltip="{{ 'domain.panel.tooltip.domain.visibility' | translate }}" + id="domainVisibility_id"> <option *ngFor="let visibility of domainVisibilityOptions" - [value]="visibility.value"> - {{visibility.key}} + [value]="visibility.value"> + {{ visibility.key }} </option> </select> - <mat-hint align="end">{{ "domain.panel.hint.domain.visibility" | translate }} + <mat-hint + align="end">{{ "domain.panel.hint.domain.visibility" | translate }} </mat-hint> </mat-form-field> - <mat-form-field *ngIf="domainResourceTypes?.length" style="width:100%"> - <mat-label>{{ "domain.panel.label.domain.default.resource.type" | translate }}</mat-label> - <select matNativeControl formControlName="defaultResourceTypeIdentifier" - matTooltip="{{ 'domain.panel.tooltip.domain.default.resource.type' | translate }}" - id="domainDefaultResourceType_id"> - <option [value]="''" disabled></option> - <option *ngFor="let resDef of domainResourceTypes" - [value]="resDef.identifier"> - {{resDef.name}} ({{resDef.identifier}}) - </option> - </select> - <mat-hint align="end">{{ "domain.panel.hint.domain.visibility" | translate }}</mat-hint> - </mat-form-field> - <mat-toolbar class ="mat-elevation-z2"> - <mat-toolbar-row class="smp-toolbar-row"> - <button id="cancelButton" mat-raised-button (click)="onResetButtonClicked()" color="primary" + <mat-form-field *ngIf="domainResourceTypes?.length" style="width:100%"> + <mat-label>{{ "domain.panel.label.domain.default.resource.type" | translate }}</mat-label> + <select matNativeControl formControlName="defaultResourceTypeIdentifier" + matTooltip="{{ 'domain.panel.tooltip.domain.default.resource.type' | translate }}" + id="domainDefaultResourceType_id"> + <option [value]="''" disabled></option> + <option *ngFor="let resDef of domainResourceTypes" + [value]="resDef.identifier"> + {{ resDef.name }} ({{ resDef.identifier }}) + </option> + </select> + <mat-hint + align="end">{{ "domain.panel.hint.domain.visibility" | translate }} + </mat-hint> + </mat-form-field> + <mat-toolbar class="mat-elevation-z2"> + <mat-toolbar-row class="smp-toolbar-row"> + <button id="cancelButton" mat-raised-button + (click)="onResetButtonClicked()" color="primary" [disabled]="!resetButtonEnabled"> <mat-icon>refresh</mat-icon> <span>{{ "domain.panel.button.reset" | translate }}</span> </button> - <button id="saveButton" mat-raised-button (click)="onSaveButtonClicked()" color="primary" + <button id="saveButton" mat-raised-button + (click)="onSaveButtonClicked()" color="primary" [disabled]="!submitButtonEnabled"> <mat-icon>save</mat-icon> <span>{{ "domain.panel.button.save" | translate }}</span> diff --git a/smp-angular/src/app/system-settings/admin-users/admin-user.service.ts b/smp-angular/src/app/system-settings/admin-users/admin-user.service.ts index e678dcafc13b2085b12eb1ba7c727facd436f1e0..708b6a19b4e85fa8d11f9b7062773a02970d9e6e 100644 --- a/smp-angular/src/app/system-settings/admin-users/admin-user.service.ts +++ b/smp-angular/src/app/system-settings/admin-users/admin-user.service.ts @@ -9,11 +9,9 @@ import {MemberRo} from "../../common/model/member-ro.model"; import {SmpConstants} from "../../smp.constants"; import {UserRo} from "../../common/model/user-ro.model"; - @Injectable() export class AdminUserService { - constructor( private http: HttpClient, private securityService: SecurityService) { diff --git a/smp-angular/src/assets/i18n/en.json b/smp-angular/src/assets/i18n/en.json index a0ccdde8b3a85c8a8992a311fe8aafce51bda099..33d514440f85b313f845fa009322c3352862f506 100644 --- a/smp-angular/src/assets/i18n/en.json +++ b/smp-angular/src/assets/i18n/en.json @@ -64,12 +64,16 @@ "member.dialog.button.save": "Save", "member.dialog.hint.choose.role": "Choose member role", "member.dialog.hint.type.username": "Type username or name to locate the user and select user from the list", + "member.dialog.hint.can.review": "Check if user has permission to review the resource/subresource documents", "member.dialog.label.choose.user": "Choose User to invite", "member.dialog.label.invite.members": "You're inviting members to {{target}}.", + "member.dialog.label.select.role.type": "Select role for the user", + "member.dialog.label.permission.review": "Permission to review", "member.dialog.placeholder.role.type": "Role for the member", "manage.dialog.title.invite.mode": "Invite {{membershipType}} member", "manage.dialog.title.edit.mode": "Edit {{membershipType}} member", "member.dialog.tooltip.role.type": "Role type for the member.", + "member.dialog.tooltip.permission.review": "Can review document.", "object.properties.dialog.button.close": "Close", "object.properties.dialog.header.key": "Key", @@ -132,8 +136,6 @@ "document.properties.panel.label.value": "Value", "document.properties.panel.label.no.properties.found": "No Document properties found", "document.properties.panel.label.select.page": "Select page of properties", - "document.properties.panel.label.expand.collapse": "Expand/Collapse", - "document.properties.panel.tooltip.expand.collapse": "Expand/Collapse property panel", "document.properties.panel.label.create": "Create", "document.properties.panel.tooltip.create": "Create new property", "document.properties.panel.label.edit": "Edit", @@ -142,6 +144,28 @@ "document.properties.panel.tooltip.delete.remove": "Delete selected property", "document.properties.panel.label.reset": "Reset changes", "document.properties.panel.tooltip.reset": "Reset changes", + "document.properties.panel.tab.title": "Document properties", + "document.properties.panel.tab.button.properties": "Properties", + + "document.events.panel.tab.title": "Current Document version events", + "document.events.panel.tab.button.events": "Events", + "document.events.panel.label.filter": "Filter", + "document.events.panel.label.date": "Date", + "document.events.panel.label.type": "Event type", + "document.events.panel.label.username": "Username", + "document.events.panel.label.source": "Event Source", + "document.events.panel.label.no.properties.found": "No DocumentVersion events found", + "document.events.panel.label.select.page": "Select page of events", + + "document.versions.panel.tab.title": "All Document Versions", + "document.versions.panel.tab.button.versions": "Versions", + "document.versions.panel.label.filter": "Filter", + "document.versions.panel.label.no.properties.found": "No Document versions found", + "document.versions.panel.label.select.page": "Select page of document versions", + "document.versions.panel.label.version": "Version", + "document.versions.panel.label.created": "Created", + "document.versions.panel.label.updated": "Last updated", + "document.versions.panel.label.status": "status", "membership.panel.button.invite.member": "Invite member", "membership.panel.button.edit.membership": "Edit", @@ -155,7 +179,9 @@ "membership.panel.label.no.data.found": "No direct members for the domain", "membership.panel.label.role.type": "Role type", "membership.panel.label.username": "Username", + "membership.panel.label.permission.review": "Can review", "membership.panel.placeholder.filter": "Member filter", + "membership.panel.title.domain": "Direct Domain members {{value}}", "membership.panel.title.group": "Direct Group members {{value}}", "membership.panel.title.resource": "Resource direct members", @@ -295,6 +321,7 @@ "resource.dialog.placeholder.resource.visibility": "Resource visibility", "resource.dialog.tooltip.resource.type": "Select type for the resource.", "resource.dialog.tooltip.resource.visibility": "Resource visibility.", + "resource.dialog.tooltip.resource.review.enabled": "Review process enabled", "group.resource.panel.button.create": "Create", "group.resource.panel.button.delete": "Delete", @@ -336,10 +363,12 @@ "document.wizard.dialog.button.ok": "OK", "document.wizard.dialog.title": "Resource Extension Wizard", + "resource.details.panel.alert.resource.saved": "Resource data are persisted.", "resource.details.panel.label.resource.id": "Resource identifier", "resource.details.panel.label.resource.name": "Edit resource document", "resource.details.panel.label.resource.scheme": "Resource scheme", "resource.details.panel.label.resource.type": "Resource type", + "resource.details.panel.label.resource.review.enabled": "Review process enabled", "resource.details.panel.label.resource.visibility": "Resource visibility", "resource.details.panel.label.resource.visibility.private": "The private resource is accessible only to the resource members!", "resource.details.panel.label.resource.visibility.private.group": "The resource belongs to the private group. Only the group members and group resource members can access the resource!", @@ -349,50 +378,57 @@ "resource.details.panel.placeholder.resource.type": "Resource type for the resource", "resource.details.panel.tooltip.resource.type": "Select type for the resource.", "resource.details.panel.tooltip.resource.visibility": "Resource visibility.", + "resource.details.panel.tooltip.resource.review.enabled": "Review process enabled", "resource.details.panel.tooltip.show.resource": "Show resource", "resource.details.panel.title": "Resources", + "resource.details.panel.button.reset": "Reset", + "resource.details.panel.button.save": "Save", + + "document.edit.panel.button.back": "Back", + "document.edit.panel.button.cancel": "Cancel", + "document.edit.panel.button.document.wizard": "Document wizard", + "document.edit.panel.button.generate": "Generate", + "document.edit.panel.button.validate": "Validate", + "document.edit.panel.button.version.new": "New version", + "document.edit.panel.button.version.publish": "Publish", + "document.edit.panel.button.version.review": "Review", + "document.edit.panel.button.version.approve": "Approve", + "document.edit.panel.button.version.reject": "Reject", + "document.edit.panel.button.save": "Save", + "document.edit.panel.cancel.confirmation.dialog.description": "Do you want to cancel all changes on the document?", + "document.edit.panel.cancel.confirmation.dialog.title": "Cancel changes", + "document.edit.panel.document.wizard.dialog.title": "Resource wizard", + "document.edit.panel.label.selected.created.on": "Created on:", + "document.edit.panel.label.selected.version": "Selected version:", + "document.edit.panel.label.selected.status": "Status:", + "document.edit.panel.placeholder.version": "All document version", + "document.edit.panel.success.save": "Document is saved with current version [{{currentResourceVersion}}].", + "document.edit.panel.success.generate": "Document is generated.", + "document.edit.panel.success.valid": "Document is Valid.", + "document.edit.panel.error.document.null": "Can not show document because it is not selected.", + "document.edit.panel.title": "Resources", + "document.edit.panel.tooltip.document.wizard": "Show document wizard dialog", + "document.edit.panel.tooltip.generate": "Generate resource", + "document.edit.panel.tooltip.save": "Save resource", + "document.edit.panel.tooltip.validate": "Validate resource", + "document.edit.panel.tooltip.version": "Select version to display.", + "document.edit.panel.tooltip.version.new": "Create new document version for the resource", + "document.edit.panel.tooltip.version.publish": "Publish selected document version for the resource", + "document.edit.panel.tooltip.version.review": "Submit selected document version for the review", + "document.edit.panel.tooltip.version.approve": "Submit selected document version for the review", + "document.edit.panel.tooltip.version.reject": "Submit selected document version for the review", + "document.edit.panel.note.editable": "Note: Only documents in following status can be edited: [{{editableDocStatusList}}]", + + "review.edit.panel.label.review": "Review", + "review.edit.panel.label.column.date": "Review date", + "review.edit.panel.label.column.version": "Version", + "review.edit.panel.label.column.target": "Target", + "review.edit.panel.label.column.resource.scheme": "Res. scheme", + "review.edit.panel.label.column.resource.value": "Res. value", + "review.edit.panel.label.column.subresource.scheme": "Subr. scheme", + "review.edit.panel.label.column.subresource.value": "Subr. value", + - "resource.document.panel.button.back": "Back", - "resource.document.panel.button.cancel": "Cancel", - "resource.document.panel.button.document.wizard": "Document wizard", - "resource.document.panel.button.generate": "Generate", - "resource.document.panel.button.validate": "Validate", - "resource.document.panel.button.save": "Save", - "resource.document.panel.cancel.confirmation.dialog.description": "Do you want to cancel all changes on the document?", - "resource.document.panel.cancel.confirmation.dialog.title": "Cancel changes", - "resource.document.panel.document.wizard.dialog.title": "Resource wizard", - "resource.document.panel.label.created.on": "Created on:", - "resource.document.panel.label.show.version": "Show version:", - "resource.document.panel.placeholder.version": "All document version", - "resource.document.panel.success.save": "Document is saved with current version [{{currentResourceVersion}}].", - "resource.document.panel.success.generate": "Document is generated.", - "resource.document.panel.success.valid": "Document is Valid.", - "resource.document.panel.title": "Resources", - "resource.document.panel.tooltip.document.wizard": "Show document wizard dialog", - "resource.document.panel.tooltip.generate": "Generate resource", - "resource.document.panel.tooltip.save": "Save resource", - "resource.document.panel.tooltip.validate": "Validate resource", - "resource.document.panel.tooltip.version": "Select version to display.", - - "subresource.document.panel.button.back": "Back", - "subresource.document.panel.button.cancel": "Cancel", - "subresource.document.panel.button.document.wizard": "Document wizard", - "subresource.document.panel.button.generate": "Generate", - "subresource.document.panel.button.validate": "Validate", - "subresource.document.panel.button.save": "Save", - "subresource.document.panel.cancel.confirmation.dialog.description": "Do you want to cancel all changes on the document?", - "subresource.document.panel.cancel.confirmation.dialog.title": "Cancel changes", - "subresource.document.panel.label.created.on": "Created on:", - "subresource.document.panel.label.show.version": "Show version:", - "subresource.document.panel.placeholder.show.version": "All document version", - "subresource.document.panel.success.generate": "Document is generated.", - "subresource.document.panel.success.save": "Document is saved with current version [{{currentResourceVersion}}].", - "subresource.document.panel.success.valid": "Document is Valid.", - "subresource.document.panel.tooltip.document.wizard": "Show document wizard dialog", - "subresource.document.panel.tooltip.generate": "Generate resource", - "subresource.document.panel.tooltip.save": "Save resource", - "subresource.document.panel.tooltip.show.version": "Select version to display.", - "subresource.document.panel.tooltip.validate": "Validate resource", "subresource.document.wizard.button.cancel": "Cancel", "subresource.document.wizard.button.ok": "OK", @@ -819,6 +855,9 @@ "toolbar.component.label.system.administrator.role": "System administrator", "toolbar.component.label.user.role": "SMP user", + "expandable.panel.label.expand.collapse": "Expand/Collapse", + "expandable.panel.tooltip.expand.collapse": "Expand/Collapse property panel", + "app.component.button.collapse": "Collapse sidebar", "app.component.tooltip.collapse": "Collapse", "app.component.tooltip.expand": "Expand", @@ -833,8 +872,10 @@ "navigation.label.edit.domains": "Edit Domains", "navigation.label.edit.groups": "Edit Groups", "navigation.label.edit.resources": "Edit Resources", + "navigation.label.edit.document.review": "Review Document", "navigation.label.edit.resource.document": "Edit Resource Document", "navigation.label.edit.subresource.document": "Edit Subresource Document", + "navigation.label.review.tasks": "Review Tasks", "navigation.label.system.settings": "System Settings", "navigation.label.system.settings.alerts": "Alerts", "navigation.label.system.settings.domains": "Domain", @@ -852,4 +893,5 @@ "navigation.tooltip.search.resources": "Search registered resources", "navigation.tooltip.search.tools": "Search tools", "navigation.tooltip.search.dns.tools": "DNS lookup tools" + } diff --git a/smp-angular/src/main.ts b/smp-angular/src/main.ts index 46c1c73e209ee7d73a0112cb31fb229314813ae6..d2d023651523c5b590f5741c2a468dafb55261c0 100644 --- a/smp-angular/src/main.ts +++ b/smp-angular/src/main.ts @@ -2,6 +2,30 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; import { environment } from './environments/environment'; import { AppModule } from './app/app.module'; +// add the following EU locales +import '@angular/common/locales/global/bg'; +import '@angular/common/locales/global/cs'; +import '@angular/common/locales/global/da'; +import '@angular/common/locales/global/de'; +import '@angular/common/locales/global/el'; +import '@angular/common/locales/global/en'; +import '@angular/common/locales/global/es'; +import '@angular/common/locales/global/et'; +import '@angular/common/locales/global/fi'; +import '@angular/common/locales/global/fr'; +import '@angular/common/locales/global/hr'; +import '@angular/common/locales/global/hu'; +import '@angular/common/locales/global/it'; +import '@angular/common/locales/global/lt'; +import '@angular/common/locales/global/lv'; +import '@angular/common/locales/global/mt'; +import '@angular/common/locales/global/nl'; +import '@angular/common/locales/global/pl'; +import '@angular/common/locales/global/pt'; +import '@angular/common/locales/global/ro'; +import '@angular/common/locales/global/sk'; +import '@angular/common/locales/global/sl'; +import '@angular/common/locales/global/sv'; if (environment.production) { enableProdMode(); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDocumentVersionToDocumentVersionROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDocumentVersionToDocumentVersionROConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..0fe8f410a7346b2b894b4df277ee4021060f273b --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBDocumentVersionToDocumentVersionROConverter.java @@ -0,0 +1,54 @@ +/*- + * #START_LICENSE# + * smp-server-library + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; +import eu.europa.ec.edelivery.smp.data.ui.DocumentVersionRO; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import org.apache.commons.beanutils.BeanUtils; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; + +/** + * Converter for DBDocumentVersion to DocumentVersionRO + * + * @author Joze RIHTARSIC + * @since 5.1 + */ +@Component +public class DBDocumentVersionToDocumentVersionROConverter implements Converter<DBDocumentVersion, DocumentVersionRO> { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DBDocumentVersionToDocumentVersionROConverter.class); + + @Override + public DocumentVersionRO convert(DBDocumentVersion source) { + + DocumentVersionRO target = new DocumentVersionRO(); + try { + BeanUtils.copyProperties(target, source); + target.setVersionStatus(source.getStatus()); + } catch (IllegalAccessException | InvocationTargetException e) { + LOG.error("Error occurred while converting DBResource", e); + return null; + } + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java index b1c96bfeb7ef02445901c6ced2531b3a552fe15f..6708e312ab87cc89c4d21371b6d7cfbf18f27c01 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceMemberToMemberROConverter.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -25,6 +25,8 @@ import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + /** * @@ -40,6 +42,7 @@ public class DBResourceMemberToMemberROConverter implements Converter<DBResource target.setFullName(source.getUser().getFullName()); target.setRoleType(source.getRole()); target.setMemberId(SessionSecurityUtils.encryptedEntityId(source.getId())); + target.setHasPermissionReview(isTrue(source.hasPermissionToReview())); return target; } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceToResourceROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceToResourceROConverter.java index 84f7167195ab6d33d543f8fd02dc7f297332fcbb..637dca1fd6469b3ad6a5ccb18194139361fdfbdc 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceToResourceROConverter.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBResourceToResourceROConverter.java @@ -45,6 +45,7 @@ public class DBResourceToResourceROConverter implements Converter<DBResource, Re BeanUtils.copyProperties(target, source); target.setResourceTypeIdentifier(source.getDomainResourceDef().getResourceDef().getIdentifier()); target.setResourceId(SessionSecurityUtils.encryptedEntityId(source.getId())); + target.setReviewEnabled(source.isReviewEnabled()); } catch (IllegalAccessException | InvocationTargetException e) { LOG.error("Error occurred while converting DBResource", e); return null; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBReviewDocumentVersionToReviewDocumentVersionROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBReviewDocumentVersionToReviewDocumentVersionROConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..2698e529d5bb26e92fe82d89ffc0fc737062b33f --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBReviewDocumentVersionToReviewDocumentVersionROConverter.java @@ -0,0 +1,60 @@ +/*- + * #START_LICENSE# + * smp-server-library + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +package eu.europa.ec.edelivery.smp.conversion; + +import eu.europa.ec.edelivery.smp.data.model.doc.DBReviewDocumentVersion; +import eu.europa.ec.edelivery.smp.data.ui.ReviewDocumentVersionRO; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.apache.commons.beanutils.BeanUtils; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; + + +/** + * Converter for DBReviewDocumentVersion to ReviewDocumentVersionRO + * + * @author Joze RIHTARSIC + * @since 5.1 + */ +@Component +public class DBReviewDocumentVersionToReviewDocumentVersionROConverter implements Converter<DBReviewDocumentVersion, ReviewDocumentVersionRO> { + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DBReviewDocumentVersionToReviewDocumentVersionROConverter.class); + + @Override + public ReviewDocumentVersionRO convert(DBReviewDocumentVersion source) { + + ReviewDocumentVersionRO target = new ReviewDocumentVersionRO(); + try { + BeanUtils.copyProperties(target, source); + target.setDocumentVersionId(SessionSecurityUtils.encryptedEntityId(source.getDocumentVersionId())); + target.setDocumentId(SessionSecurityUtils.encryptedEntityId(source.getDocumentId())); + target.setResourceId(SessionSecurityUtils.encryptedEntityId(source.getResourceId())); + target.setSubresourceId(SessionSecurityUtils.encryptedEntityId(source.getSubresourceId())); + target.setCurrentStatus(source.getStatus()); + } catch (IllegalAccessException | InvocationTargetException e) { + LOG.error("Error occurred while converting DBResource", e); + return null; + } + return target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDao.java index b3f7542653983820b195b2e5177e77eab7bdd0c5..41367b1bd43adb803e6f980ad390ff4be390acbf 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDao.java @@ -19,10 +19,8 @@ package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; -import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; -import eu.europa.ec.edelivery.smp.data.model.doc.DBResource; -import eu.europa.ec.edelivery.smp.data.model.doc.DBSubresource; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.model.doc.*; import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import org.springframework.stereotype.Repository; @@ -133,8 +131,20 @@ public class DocumentDao extends BaseDao<DBDocument> { * @return document version list */ public List<DBDocumentVersion> getDocumentVersionsForSubresource(DBSubresource subresource) { - TypedQuery<DBDocumentVersion> query = memEManager.createNamedQuery(QUERY_DOCUMENT_VERSION_LIST_FOR_SUBRESOURCE, DBDocumentVersion.class); + TypedQuery<DBDocumentVersion> query = memEManager.createNamedQuery(QUERY_DOCUMENT_VERSION_LIST_FOR_SUBRESOURCE, + DBDocumentVersion.class); query.setParameter(PARAM_SUBRESOURCE_ID, subresource.getId()); return query.getResultList(); } + + public List<DBReviewDocumentVersion> getDocumentReviewListForUser(Long dbUserId) { + TypedQuery<DBReviewDocumentVersion> query = memEManager.createNamedQuery( + QUERY_DOCUMENT_VERSION_UNDER_REVIEW_FOR_USER, DBReviewDocumentVersion.class); + query.setParameter(PARAM_USER_ID, dbUserId); + query.setParameter(PARAM_PERMISSION_CAN_REVIEW, true); + query.setParameter(PARAM_REVIEW_ENABLED, true); + query.setParameter(PARAM_STATUS, DocumentVersionStatusType.UNDER_REVIEW.name()); + return query.getResultList(); + + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java index 5b77e0bfa65e78a49bf3c67a275d9d3de33da7c4..3906c65d563f0aa85a655082f999baed9da59e2f 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/QueryNames.java @@ -121,7 +121,9 @@ public class QueryNames { public static final String QUERY_RESOURCE_DEF_BY_IDENTIFIER_EXTENSION = "DBExtResourceDef.getByIdentifierExtension"; public static final String QUERY_DOCUMENT_FOR_RESOURCE = "DBDocument.getForResource"; + public static final String QUERY_DOCUMENT_BY_RESOURCE_DEF_SHARING = "DBDocument.getForResourceDEfAndSharingEnabled"; public static final String QUERY_DOCUMENT_FOR_SUBRESOURCE = "DBDocument.getForSubresource"; + public static final String QUERY_DOCUMENT_BY_SUBRESOURCE_DEF_SHARING = "DBDocument.getForSubresourceDEfAndSharingEnabled"; public static final String QUERY_DOCUMENT_VERSION_CURRENT_FOR_RESOURCE = "DBDocumentVersion.forCurrentForResource"; public static final String QUERY_DOCUMENT_VERSION_LIST_FOR_RESOURCE = "DBDocumentVersion.getAllForResource"; @@ -129,6 +131,7 @@ public class QueryNames { public static final String QUERY_DOCUMENT_VERSION_CURRENT_FOR_SUBRESOURCE = "DBDocumentVersion.forCurrentForSubresource"; public static final String QUERY_DOCUMENT_VERSION_LIST_FOR_SUBRESOURCE = "DBDocumentVersion.getAllForSubresource"; + public static final String QUERY_DOCUMENT_VERSION_UNDER_REVIEW_FOR_USER = "DBDocumentVersion.getAllReviewTasksForUser"; public static final String QUERY_GROUP_MEMBER_ALL = "DBGroupMember.getAll"; public static final String QUERY_GROUP_MEMBER_BY_USER_GROUPS_COUNT = "DBGroupMember.getByUserAndGroupsCount"; @@ -182,6 +185,7 @@ public class QueryNames { public static final String PARAM_RESOURCE_DEF_IDENTIFIER = "resource_def_identifier"; public static final String PARAM_SUBRESOURCE_DEF_ID = "subresource_def_id"; + public static final String PARAM_REVIEW_ENABLED = "review_enabled"; public static final String PARAM_SUBRESOURCE_DEF_IDENTIFIER = "subresource_def_identifier"; public static final String PARAM_DOMAIN_ID = "domain_id"; @@ -192,10 +196,13 @@ public class QueryNames { public static final String PARAM_DOCUMENT_ID = "document_id"; public static final String PARAM_DOCUMENT_TYPE = "document_type"; + public static final String PARAM_SHARING_ENABLED = "sharing_enabled"; + public static final String PARAM_STATUS = "status"; public static final String PARAM_GROUP_ID = "group_id"; public static final String PARAM_GROUP_IDS = "group_ids"; public static final String PARAM_MEMBERSHIP_ROLE = "membership_role"; + public static final String PARAM_PERMISSION_CAN_REVIEW = "permission_can_review"; public static final String PARAM_MEMBERSHIP_ROLES = "membership_roles"; public static final String PARAM_USER_USERNAME = "username"; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java index 483dc2b0a343ffdd1d7105caeb31283b29762803..2107b59a4093387370d1154618cb3aa76db203c7 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceMemberDao.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -56,7 +56,7 @@ public class ResourceMemberDao extends BaseDao<DBResourceMember> { } public boolean isUserResourceMemberWithRole(Long userId, Long resourceId, MembershipRoleType roleType) { - LOG.debug("User id [{}], Domain id [{}], role [{}]", userId, resourceId, roleType); + LOG.debug("User id [{}], Resource id [{}], role [{}]", userId, resourceId, roleType); TypedQuery<DBResourceMember> query = memEManager.createNamedQuery(QUERY_RESOURCE_MEMBER_BY_USER_RESOURCE, DBResourceMember.class); query.setParameter(PARAM_USER_ID, userId); @@ -64,6 +64,16 @@ public class ResourceMemberDao extends BaseDao<DBResourceMember> { return query.getResultList().stream().anyMatch(member -> member.getRole() == roleType); } + + public boolean isUserResourceMemberWithReviewPermission(Long userId, Long resourceId) { + LOG.debug("User id [{}], Resource id [{}], with review permission", userId, resourceId); + TypedQuery<DBResourceMember> query = memEManager.createNamedQuery(QUERY_RESOURCE_MEMBER_BY_USER_RESOURCE, DBResourceMember.class); + + query.setParameter(PARAM_USER_ID, userId); + query.setParameter(PARAM_RESOURCE_ID, resourceId); + return query.getResultList().stream().anyMatch(DBResourceMember::hasPermissionToReview); + } + public boolean isUserAnyDomainResourceMember(DBUser user, DBDomain domain) { LOG.debug("User [{}], Domain [{}]", user, domain); TypedQuery<Long> query = memEManager.createNamedQuery(QUERY_RESOURCE_MEMBER_BY_USER_DOMAIN_RESOURCE_COUNT, Long.class); @@ -151,11 +161,14 @@ public class ResourceMemberDao extends BaseDao<DBResourceMember> { } - public DBResourceMember addMemberToResource(DBResource resource, DBUser user, MembershipRoleType role) { + public DBResourceMember addMemberToResource(DBResource resource, DBUser user, + MembershipRoleType role, + boolean hasPermissionReview) { DBResourceMember resourceMember = new DBResourceMember(); resourceMember.setRole(role); resourceMember.setUser(user); resourceMember.setResource(resource); + resourceMember.setHasPermissionToReview(hasPermissionReview); resourceMember = merge(resourceMember); return resourceMember; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionEventType.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionEventType.java new file mode 100644 index 0000000000000000000000000000000000000000..7dc30b30b45f1a2e10dd5c7dec0c0a86acb922a4 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionEventType.java @@ -0,0 +1,19 @@ +package eu.europa.ec.edelivery.smp.data.enums; + +/** + * Document version event types. The event status allows user to track + * changes in the document version. + * + * @author Joze Rihtarsic + * @since 5.1 + */ +public enum DocumentVersionEventType { + CREATE, + UPDATE, + PUBLISH, + REQUEST_REVIEW, + RETIRE, + APPROVE, + REJECT, + ERROR +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionStatusType.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionStatusType.java new file mode 100644 index 0000000000000000000000000000000000000000..a19af0f902b29d62a39b5e0254d7c2e192a760c0 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/DocumentVersionStatusType.java @@ -0,0 +1,11 @@ +package eu.europa.ec.edelivery.smp.data.enums; + + +public enum DocumentVersionStatusType { + DRAFT, + PUBLISHED, + RETIRED, + UNDER_REVIEW, + APPROVED, + REJECTED, +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/EventSourceType.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/EventSourceType.java new file mode 100644 index 0000000000000000000000000000000000000000..d64e3b85bac7f04e7c981d28846e78859b587a62 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/EventSourceType.java @@ -0,0 +1,16 @@ +package eu.europa.ec.edelivery.smp.data.enums; + +/** + * Document version event source types. The event source can be UI, REST API, + * Automatic cron or any other custom plugin. + * + * @author Joze Rihtarsic + * @since 5.1 + */ +public enum EventSourceType { + UI, + REST_API, + CRON, + PLUGIN, + OTHER +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocument.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocument.java index 5e9d842eeae664501f16579ae9c1726cc233c1b7..9456463925e90f56e6aba2e52071701c130bf3b4 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocument.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocument.java @@ -19,6 +19,7 @@ package eu.europa.ec.edelivery.smp.data.model.doc; import eu.europa.ec.edelivery.smp.data.dao.utils.ColumnDescription; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; import eu.europa.ec.edelivery.smp.data.model.BaseEntity; import eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths; import eu.europa.ec.edelivery.smp.logging.SMPLogger; @@ -31,8 +32,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.QUERY_DOCUMENT_FOR_RESOURCE; -import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.QUERY_DOCUMENT_FOR_SUBRESOURCE; +import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; /** * Database optimization: load service metadata xml only when needed and @@ -46,10 +46,17 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.QUERY_DOCUMENT_FOR_ @Audited @Table(name = "SMP_DOCUMENT") @org.hibernate.annotations.Table(appliesTo = "SMP_DOCUMENT", comment = "SMP document entity for resources and subresources") -@NamedQueries({ - @NamedQuery(name = QUERY_DOCUMENT_FOR_RESOURCE, query = "SELECT d FROM DBResource r JOIN r.document d WHERE r.id =:resource_id"), + + @NamedQuery(name = QUERY_DOCUMENT_FOR_RESOURCE, query = "SELECT d FROM DBResource r JOIN r.document d WHERE r.id =:resource_id") + @NamedQuery(name = QUERY_DOCUMENT_BY_RESOURCE_DEF_SHARING, query = "SELECT d FROM DBResource r " + + "INNER JOIN r.document d " + + "INNER JOIN r.domainResourceDef.resourceDef rdef " + + " WHERE rdef.identifier =:resource_def_identifier and d.sharingEnabled =:sharing_enabled") @NamedQuery(name = QUERY_DOCUMENT_FOR_SUBRESOURCE, query = "SELECT d FROM DBSubresource sr JOIN sr.document d WHERE sr.id =:subresource_id") -}) + @NamedQuery(name = QUERY_DOCUMENT_BY_SUBRESOURCE_DEF_SHARING, query = "SELECT d FROM DBSubresource rs " + + "INNER JOIN rs.document d " + + "INNER JOIN rs.subresourceDef rdef " + + " WHERE rdef.identifier =:subresource_def_identifier and d.sharingEnabled =:sharing_enabled") public class DBDocument extends BaseEntity { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DBDocument.class); @Id @@ -59,6 +66,10 @@ public class DBDocument extends BaseEntity { @ColumnDescription(comment = "Unique document id") Long id; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "FK_REF_DOCUMENT_ID") + private DBDocument referenceDocument; + // list of all version with the latest version first! @OneToMany( mappedBy = "document", @@ -66,6 +77,7 @@ public class DBDocument extends BaseEntity { orphanRemoval = true, fetch = FetchType.LAZY ) + @OrderBy("id DESC") List<DBDocumentVersion> documentVersions; @Column(name = "CURRENT_VERSION", nullable = false) @@ -78,6 +90,9 @@ public class DBDocument extends BaseEntity { @Column(name = "NAME") private String name; + @Column(name = "SHARING_ENABLED") + private Boolean sharingEnabled = Boolean.FALSE; + @OneToMany( mappedBy = "document", cascade = CascadeType.ALL, @@ -95,6 +110,13 @@ public class DBDocument extends BaseEntity { this.id = id; } + public DBDocument getReferenceDocument() { + return referenceDocument; + } + + public void setReferenceDocument(DBDocument referenceDocument) { + this.referenceDocument = referenceDocument; + } /** * Returns document version ordered from the latest version to first version @@ -108,15 +130,25 @@ public class DBDocument extends BaseEntity { return documentVersions; } + /** + * Method add new document version to the document and set the version number. + * The version number is set to the highest version number + 1 and set as current version. + * Also existing published version is set to retired. + * + * @param documentVersion document version + * @return document version + */ public DBDocumentVersion addNewDocumentVersion(DBDocumentVersion documentVersion) { if (documentVersion.getId() != null && getDocumentVersions().contains(documentVersion)) { LOG.info("Document version [{}] already exists on document [{}]", documentVersion, this); return documentVersion; - } + } + documentVersion.setVersion(getNextVersionIndex()); - getDocumentVersions().add(documentVersion); documentVersion.setDocument(this); setCurrentVersion(documentVersion.getVersion()); + // ADD TO THE LIST to the first position (latest version) + getDocumentVersions().add(0, documentVersion); return documentVersion; } @@ -135,7 +167,6 @@ public class DBDocument extends BaseEntity { .reduce(0, Integer::max) + 1; } - public int getCurrentVersion() { return currentVersion; } @@ -155,6 +186,14 @@ public class DBDocument extends BaseEntity { getDocumentProperties().removeIf(DBDocumentProperty::isTransient); } + public Boolean getSharingEnabled() { + return sharingEnabled; + } + + public void setSharingEnabled(Boolean sharingEnabled) { + this.sharingEnabled = sharingEnabled; + } + @Override public void prePersist() { super.prePersist(); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersion.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersion.java index 02cd71db827e029e6cf52c0a941ebd9f887ad4e4..293fff778281519481adc9fda72d0cc2c4ea3e60 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersion.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersion.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -19,11 +19,16 @@ package eu.europa.ec.edelivery.smp.data.model.doc; import eu.europa.ec.edelivery.smp.data.dao.utils.ColumnDescription; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; import eu.europa.ec.edelivery.smp.data.model.BaseEntity; import org.hibernate.annotations.GenericGenerator; import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; import javax.persistence.*; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; @@ -45,25 +50,68 @@ import static eu.europa.ec.edelivery.smp.data.dao.QueryNames.*; }) @org.hibernate.annotations.Table(appliesTo = "SMP_DOCUMENT_VERSION", comment = "Document content for the document version.") - -@NamedQueries({ - @NamedQuery(name = QUERY_DOCUMENT_VERSION_CURRENT_FOR_RESOURCE, query = "SELECT dv FROM DBResource r join r.document d join d.documentVersions dv " + - " WHERE dv.version = d.currentVersion " + - " AND r.id= :resource_id "), - @NamedQuery(name = QUERY_DOCUMENT_VERSION_LIST_FOR_RESOURCE, query = "SELECT dv FROM DBResource r join r.document.documentVersions dv " + - " WHERE r.id= :resource_id order by dv.version desc"), - - @NamedQuery(name = QUERY_DOCUMENT_VERSION_CURRENT_FOR_SUBRESOURCE, query = "SELECT dv FROM " + - " DBSubresource sr join sr.document d join d.documentVersions dv " + - " WHERE dv.version = d.currentVersion " + - " AND sr.id= :subresource_id "), - @NamedQuery(name = QUERY_DOCUMENT_VERSION_LIST_FOR_SUBRESOURCE, query = "SELECT dv FROM " + - " DBSubresource sr join sr.document.documentVersions dv " + - " WHERE sr.id= :subresource_id order by dv.version desc") -}) - +@NamedQuery(name = QUERY_DOCUMENT_VERSION_CURRENT_FOR_RESOURCE, query = "SELECT dv FROM DBResource r join r.document d join d.documentVersions dv " + + " WHERE dv.version = d.currentVersion " + + " AND r.id= :resource_id ") +@NamedQuery(name = QUERY_DOCUMENT_VERSION_LIST_FOR_RESOURCE, query = "SELECT dv FROM DBResource r join r.document.documentVersions dv " + + " WHERE r.id= :resource_id order by dv.version desc") +@NamedQuery(name = QUERY_DOCUMENT_VERSION_CURRENT_FOR_SUBRESOURCE, query = "SELECT dv FROM " + + " DBSubresource sr join sr.document d join d.documentVersions dv " + + " WHERE dv.version = d.currentVersion " + + " AND sr.id= :subresource_id ") +@NamedQuery(name = QUERY_DOCUMENT_VERSION_LIST_FOR_SUBRESOURCE, query = "SELECT dv FROM " + + " DBSubresource sr join sr.document.documentVersions dv " + + " WHERE sr.id= :subresource_id order by dv.version desc") +@NamedNativeQuery(name = QUERY_DOCUMENT_VERSION_UNDER_REVIEW_FOR_USER, + query = "SELECT " + + " dv.ID AS ID, " + + " dv.LAST_UPDATED_ON AS LAST_UPDATED_ON," + + " dv.FK_DOCUMENT_ID AS DOCUMENT_ID," + + " dv.STATUS AS STATUS," + + " dv.VERSION AS VERSION," + + " r.ID AS RESOURCE_ID," + + " sr.ID AS SUBRESOURCE_ID," + + " r.IDENTIFIER_VALUE AS RIDENTIFIER_VALUE," + + " r.IDENTIFIER_SCHEME AS RIDENTIFIER_SCHEME," + + " sr.IDENTIFIER_VALUE AS SRIDENTIFIER_VALUE," + + " sr.IDENTIFIER_SCHEME AS SRIDENTIFIER_SCHEME," + + " CASE " + + " WHEN sr.ID IS NOT NULL THEN 'SUBRESOURCE'" + + " ELSE 'RESOURCE'" + + " END AS TARGET" + + " FROM " + + " SMP_DOCUMENT_VERSION dv" + + " INNER JOIN SMP_DOCUMENT d ON dv.FK_DOCUMENT_ID = d.ID" + + " LEFT JOIN SMP_SUBRESOURCE sr ON d.ID = sr.FK_DOCUMENT_ID" + + " LEFT JOIN SMP_RESOURCE r ON d.ID = r.FK_DOCUMENT_ID OR r.ID = sr.FK_RESOURCE_ID" + + " INNER JOIN SMP_RESOURCE_MEMBER rmu ON r.ID = rmu.FK_RESOURCE_ID" + + " WHERE " + + " dv.STATUS = :status" + + " AND r.REVIEW_ENABLED = :review_enabled" + + " AND rmu.FK_USER_ID = :user_id" + + " AND rmu.PERMISSION_REVIEW = :permission_can_review", + + resultSetMapping = "DBReviewDocumentVersionsMapping") + +@SqlResultSetMapping(name = "DBReviewDocumentVersionsMapping", + classes = { + @ConstructorResult(targetClass = DBReviewDocumentVersion.class, + columns = { + @ColumnResult(name = "ID", type = Long.class), + @ColumnResult(name = "DOCUMENT_ID", type = Long.class), + @ColumnResult(name = "RESOURCE_ID", type = Long.class), + @ColumnResult(name = "SUBRESOURCE_ID", type = Long.class), + @ColumnResult(name = "VERSION", type = Integer.class), + @ColumnResult(name = "STATUS", type = String.class), + @ColumnResult(name = "RIDENTIFIER_VALUE", type = String.class), + @ColumnResult(name = "RIDENTIFIER_SCHEME", type = String.class), + @ColumnResult(name = "SRIDENTIFIER_VALUE", type = String.class), + @ColumnResult(name = "SRIDENTIFIER_SCHEME", type = String.class), + @ColumnResult(name = "TARGET", type = String.class), + @ColumnResult(name = "LAST_UPDATED_ON", type = OffsetDateTime.class), + }) + }) public class DBDocumentVersion extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "SMP_DOCUMENT_VERSION_SEQ") @GenericGenerator(name = "SMP_DOCUMENT_VERSION_SEQ", strategy = "native") @@ -74,11 +122,19 @@ public class DBDocumentVersion extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "FK_DOCUMENT_ID") private DBDocument document; - + // list of all document events with the latest event first! + @OneToMany( + mappedBy = "documentVersion", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) + @NotAudited + @OrderBy("id desc") + List<DBDocumentVersionEvent> documentVersionEvents; + // version of the document @Column(name = "VERSION", nullable = false) private int version; - - // lob fetch it only when needed! @Lob @Basic(fetch = FetchType.LAZY) @@ -86,6 +142,11 @@ public class DBDocumentVersion extends BaseEntity { @ColumnDescription(comment = "Document content") byte[] content; + @Enumerated(EnumType.STRING) + @Column(name = "STATUS", nullable = false) + @ColumnDescription(comment = "Document version status") + private DocumentVersionStatusType status = DocumentVersionStatusType.DRAFT; + @Override public Long getId() { return id; @@ -119,6 +180,39 @@ public class DBDocumentVersion extends BaseEntity { this.content = content; } + public DocumentVersionStatusType getStatus() { + return status; + } + + public void setStatus(DocumentVersionStatusType status) { + this.status = status; + } + + /** + * Returns document version events + * + * @return list of events for the document versions + */ + public List<DBDocumentVersionEvent> getDocumentVersionEvents() { + if (documentVersionEvents == null) { + documentVersionEvents = new ArrayList<>(); + } + return documentVersionEvents; + } + + /** + * Add new document version event. Beause of the order of the events, + * the new event is added to the beginning of the list.* + * + * @param event event to be added + * @return added event + */ + public DBDocumentVersionEvent addNewDocumentVersionEvent(DBDocumentVersionEvent event) { + event.setDocumentVersion(this); + getDocumentVersionEvents().add(0, event); + return event; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -132,4 +226,14 @@ public class DBDocumentVersion extends BaseEntity { public int hashCode() { return Objects.hash(super.hashCode(), id); } + + @Override + public String toString() { + return "DBDocumentVersion{" + + "id=" + id + + ", document=" + document.id + + ", version=" + version + + ", status=" + status + + '}'; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersionEvent.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersionEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..54fa7c8dc2274b4164451c4c68b4e19067fdd3b6 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBDocumentVersionEvent.java @@ -0,0 +1,159 @@ +/*- + * #START_LICENSE# + * smp-server-library + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +package eu.europa.ec.edelivery.smp.data.model.doc; + +import eu.europa.ec.edelivery.smp.data.dao.utils.ColumnDescription; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; +import eu.europa.ec.edelivery.smp.data.model.BaseEntity; +import eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Document version event entity. The event entity allows user to track + * changes in the document version. Note that the event is audited, because + * the record is a special audit record, and it is not expected + * + * @author Joze Rihtarsic + * @since 5.1 + */ +@Entity +@Table(name = "SMP_DOCUMENT_VERSION_EVENT", + indexes = { + @Index(name = "SMP_DOCVEREVNT_DOCVER_IDX", columnList = "FK_DOCUMENT_VERSION_ID"), + }) +@org.hibernate.annotations.Table(appliesTo = "SMP_DOCUMENT_VERSION_EVENT", comment = "Document version Events.") +public class DBDocumentVersionEvent extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SMP_DOCVER_EVENT_SEQ") + @GenericGenerator(name = "SMP_DOCVER_EVENT_SEQ", strategy = "native") + @Column(name = "ID") + @ColumnDescription(comment = "Unique document version event identifier") + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "FK_DOCUMENT_VERSION_ID") + private DBDocumentVersion documentVersion; + + @Enumerated(EnumType.STRING) + @Column(name = "EVENT_TYPE", nullable = false) + @ColumnDescription(comment = "Document version event type") + private DocumentVersionEventType eventType = DocumentVersionEventType.CREATE; + + @Column(name = "EVENT_ON") + @ColumnDescription(comment = "Date time of the event") + private OffsetDateTime eventOn; + + @Column(name = "EVENT_BY_USERNAME", length = CommonColumnsLengths.MAX_USERNAME_LENGTH) + @ColumnDescription(comment = "username identifier of the user who triggered the event") + private String username; + + @Enumerated(EnumType.STRING) + @Column(name = "EVENT_SOURCE", nullable = false) + @ColumnDescription(comment = "Event source UI, API") + private EventSourceType eventSourceType = EventSourceType.OTHER; + + @Column(name = "DETAILS", length = CommonColumnsLengths.MAX_MEDIUM_TEXT_LENGTH) + @ColumnDescription(comment = "Details of the event") + private String details; + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public DBDocumentVersion getDocumentVersion() { + return documentVersion; + } + + public void setDocumentVersion(DBDocumentVersion documentVersion) { + this.documentVersion = documentVersion; + } + + public DocumentVersionEventType getEventType() { + return eventType; + } + + public void setEventType(DocumentVersionEventType eventType) { + this.eventType = eventType; + } + + public OffsetDateTime getEventOn() { + return eventOn; + } + + public void setEventOn(OffsetDateTime eventOn) { + this.eventOn = eventOn; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public EventSourceType getEventSourceType() { + return eventSourceType; + } + + public void setEventSourceType(EventSourceType eventSourceType) { + this.eventSourceType = eventSourceType; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DBDocumentVersionEvent that = (DBDocumentVersionEvent) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id); + } + + @Override + public String toString() { + return "DBDocumentVersion{" + + "id=" + id + + ", documentVersion=" + documentVersion.id + + '}'; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBResource.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBResource.java index 1f1b06cf4b7264f4e468775751e819df197bfc55..86e27f001a79936045a519d082913d770147dd9a 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBResource.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBResource.java @@ -151,9 +151,12 @@ public class DBResource extends BaseEntity { @Column(name = "IDENTIFIER_SCHEME", length = CommonColumnsLengths.MAX_IDENTIFIER_VALUE_SCHEME_LENGTH) String identifierScheme; - @Column(name = "SML_REGISTERED", nullable = false) + @Column(name = "SML_REGISTERED") private boolean smlRegistered = false; + @Column(name = "REVIEW_ENABLED") + private Boolean reviewEnabled = Boolean.FALSE; + @Enumerated(EnumType.STRING) @Column(name = "VISIBILITY", length = CommonColumnsLengths.MAX_TEXT_LENGTH_128) private VisibilityType visibility = VisibilityType.PUBLIC; @@ -271,6 +274,14 @@ public class DBResource extends BaseEntity { this.smlRegistered = smlRegistered; } + public Boolean isReviewEnabled() { + return reviewEnabled == null ? Boolean.FALSE : reviewEnabled; + } + + public void setReviewEnabled(Boolean reviewEnabled) { + this.reviewEnabled = reviewEnabled; + } + /** * Id is database suragete id + natural key! * diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBReviewDocumentVersion.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBReviewDocumentVersion.java new file mode 100644 index 0000000000000000000000000000000000000000..f34bfdcb66adf7f4aaef2eaaab1fe5d26a03998d --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/doc/DBReviewDocumentVersion.java @@ -0,0 +1,156 @@ +package eu.europa.ec.edelivery.smp.data.model.doc; + +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.time.OffsetDateTime; + +/** + * Class represents the document version for the review. It is used with query + * to get all review tasks for the user + * + * @author Joze RIHARSIC + * @since 5.1 + */ +public class DBReviewDocumentVersion implements Serializable { + + private Long documentId; + private Long documentVersionId; + private Long resourceId; + private Long subresourceId; + private int version; + private DocumentVersionStatusType status = DocumentVersionStatusType.DRAFT; + private String resourceIdentifierValue; + private String resourceIdentifierScheme; + private String subresourceIdentifierValue; + private String subresourceIdentifierScheme; + private String target; + private OffsetDateTime lastUpdatedOn; + + public DBReviewDocumentVersion() { + } + + public DBReviewDocumentVersion( + Long id, + Long documentId, + Long resourceId, + Long subresourceId, + int version, + String status, + String resourceIdentifierValue, + String resourceIdentifierScheme, + String subresourceIdentifierValue, + String subresourceIdentifierScheme, + String target, + OffsetDateTime lastUpdatedOn) { + this.documentId = documentId; + this.documentVersionId = id; + this.resourceId = resourceId; + this.subresourceId = subresourceId; + this.version = version; + this.status = StringUtils.isNotBlank(status) ? DocumentVersionStatusType.valueOf(status) : null; + this.resourceIdentifierValue = resourceIdentifierValue; + this.resourceIdentifierScheme = resourceIdentifierScheme; + this.subresourceIdentifierValue = subresourceIdentifierValue; + this.subresourceIdentifierScheme = subresourceIdentifierScheme; + this.target = target; + this.lastUpdatedOn = lastUpdatedOn; + } + + public Long getDocumentId() { + return documentId; + } + + public void setDocumentId(Long documentId) { + this.documentId = documentId; + } + + public Long getDocumentVersionId() { + return documentVersionId; + } + + public void setDocumentVersionId(Long documentVersionId) { + this.documentVersionId = documentVersionId; + } + + public Long getResourceId() { + return resourceId; + } + + public void setResourceId(Long resourceId) { + this.resourceId = resourceId; + } + + public Long getSubresourceId() { + return subresourceId; + } + + public void setSubresourceId(Long subresourceId) { + this.subresourceId = subresourceId; + } + + public OffsetDateTime getLastUpdatedOn() { + return lastUpdatedOn; + } + + public void setLastUpdatedOn(OffsetDateTime lastUpdatedOn) { + this.lastUpdatedOn = lastUpdatedOn; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public DocumentVersionStatusType getStatus() { + return status; + } + + public void setStatus(DocumentVersionStatusType status) { + this.status = status; + } + + public String getResourceIdentifierValue() { + return resourceIdentifierValue; + } + + public void setResourceIdentifierValue(String resourceIdentifierValue) { + this.resourceIdentifierValue = resourceIdentifierValue; + } + + public String getResourceIdentifierScheme() { + return resourceIdentifierScheme; + } + + public void setResourceIdentifierScheme(String resourceIdentifierScheme) { + this.resourceIdentifierScheme = resourceIdentifierScheme; + } + + public String getSubresourceIdentifierValue() { + return subresourceIdentifierValue; + } + + public void setSubresourceIdentifierValue(String subresourceIdentifierValue) { + this.subresourceIdentifierValue = subresourceIdentifierValue; + } + + public String getSubresourceIdentifierScheme() { + return subresourceIdentifierScheme; + } + + public void setSubresourceIdentifierScheme(String subresourceIdentifierScheme) { + this.subresourceIdentifierScheme = subresourceIdentifierScheme; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java index ad1cd56f7c7bec3644f0109f485366cc21433fc9..b736ac5ca4aa0c24960c96904356a4db10060555 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/user/DBResourceMember.java @@ -18,6 +18,7 @@ */ package eu.europa.ec.edelivery.smp.data.model.user; +import eu.europa.ec.edelivery.smp.data.dao.utils.ColumnDescription; import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.BaseEntity; import eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths; @@ -84,6 +85,10 @@ public class DBResourceMember extends BaseEntity { @Column(name = "MEMBERSHIP_ROLE", length = CommonColumnsLengths.MAX_TEXT_LENGTH_64) private MembershipRoleType role = MembershipRoleType.VIEWER; + @Column(name = "PERMISSION_REVIEW") + @ColumnDescription(comment = "User permission to review the resource document") + private Boolean hasPermissionToReview = false; + public DBResourceMember() { //Need this method for hibernate // Caused by: java.lang.NoSuchMethodException: eu.europa.ec.edelivery.smp.data.model.DBServiceGroupDomain_$$_jvst7ad_2.<init>() @@ -128,4 +133,12 @@ public class DBResourceMember extends BaseEntity { public void setRole(MembershipRoleType role) { this.role = role; } + + public Boolean hasPermissionToReview() { + return hasPermissionToReview; + } + + public void setHasPermissionToReview(Boolean permissionReview) { + this.hasPermissionToReview = permissionReview; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentRO.java index d115312a5b676e50b51817f7f95cbcb9e2768e5e..bfb1199bdf5f928e100b4bb847e0d4f8473e2480 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentRO.java @@ -19,6 +19,7 @@ package eu.europa.ec.edelivery.smp.data.ui; import eu.europa.ec.edelivery.smp.config.enums.SMPPropertyTypeEnum; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; import java.time.OffsetDateTime; @@ -26,18 +27,24 @@ import java.util.ArrayList; import java.util.List; public class DocumentRO extends BaseRO { - + private static final long serialVersionUID = 9008583888835630038L; String documentId; + String referenceDocumentId; String mimeType; Integer currentResourceVersion; List<Integer> allVersions; String name; + Boolean sharingEnabled = Boolean.FALSE; + Integer payloadVersion; String payload; private int payloadStatus = EntityROStatus.PERSISTED.getStatusNumber(); OffsetDateTime payloadCreatedOn; + DocumentVersionStatusType documentVersionStatus; List<DocumentPropertyRO> properties = new ArrayList<>(); + List<DocumentVersionEventRO> documentVersionEvents = new ArrayList<>(); + List<DocumentVersionRO> documentVersions = new ArrayList<>(); public String getDocumentId() { return documentId; @@ -47,6 +54,14 @@ public class DocumentRO extends BaseRO { this.documentId = documentId; } + public String getReferenceDocumentId() { + return referenceDocumentId; + } + + public void setReferenceDocumentId(String referenceDocumentId) { + this.referenceDocumentId = referenceDocumentId; + } + public String getMimeType() { return mimeType; } @@ -59,6 +74,14 @@ public class DocumentRO extends BaseRO { return currentResourceVersion; } + public Boolean getSharingEnabled() { + return sharingEnabled; + } + + public void setSharingEnabled(Boolean sharingEnabled) { + this.sharingEnabled = sharingEnabled; + } + public void setCurrentResourceVersion(Integer currentResourceVersion) { this.currentResourceVersion = currentResourceVersion; } @@ -86,6 +109,14 @@ public class DocumentRO extends BaseRO { this.payloadVersion = payloadVersion; } + public DocumentVersionStatusType getDocumentVersionStatus() { + return documentVersionStatus; + } + + public void setDocumentVersionStatus(DocumentVersionStatusType documentVersionStatus) { + this.documentVersionStatus = documentVersionStatus; + } + public String getPayload() { return payload; } @@ -120,4 +151,20 @@ public class DocumentRO extends BaseRO { this.properties.add(propertyRO); } + + public List<DocumentVersionRO> getDocumentVersions() { + return documentVersions; + } + + public void setDocumentVersions(List<DocumentVersionRO> documentVersions) { + this.documentVersions = documentVersions; + } + + public List<DocumentVersionEventRO> getDocumentVersionEvents() { + return documentVersionEvents; + } + + public void addDocumentVersionEvent(DocumentVersionEventRO event) { + this.documentVersionEvents.add(event); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionEventRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionEventRO.java new file mode 100644 index 0000000000000000000000000000000000000000..da9f630f73c4f35957b6e49d93901da5494efd00 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionEventRO.java @@ -0,0 +1,66 @@ +package eu.europa.ec.edelivery.smp.data.ui; + + +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; + +import java.time.OffsetDateTime; + +/** + * Document version event. The event entity allows user to track + * changes in the document version. + * + * @author Joze Rihtarsic + * @since 5.1 + */ +public class DocumentVersionEventRO extends BaseRO { + + private static final long serialVersionUID = 9008583888835630037L; + + private DocumentVersionEventType eventType = DocumentVersionEventType.CREATE; + private OffsetDateTime eventOn; + private String username; + private EventSourceType eventSourceType = EventSourceType.OTHER; + private String details; + + + public DocumentVersionEventType getEventType() { + return eventType; + } + + public void setEventType(DocumentVersionEventType eventType) { + this.eventType = eventType; + } + + public OffsetDateTime getEventOn() { + return eventOn; + } + + public void setEventOn(OffsetDateTime eventOn) { + this.eventOn = eventOn; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public EventSourceType getEventSourceType() { + return eventSourceType; + } + + public void setEventSourceType(EventSourceType eventSourceType) { + this.eventSourceType = eventSourceType; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionRO.java new file mode 100644 index 0000000000000000000000000000000000000000..a09a8bd302cca95b9f248bce941016a9ceb49c73 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/DocumentVersionRO.java @@ -0,0 +1,59 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; + +import java.io.Serializable; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +public class DocumentVersionRO implements Serializable { + private static final long serialVersionUID = 9008583888835630039L; + + private int version; + private DocumentVersionStatusType versionStatus; + private OffsetDateTime createdOn; + private OffsetDateTime lastUpdatedOn; + + + List<DocumentVersionEventRO> documentVersionEvents = new ArrayList<>(); + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public DocumentVersionStatusType getVersionStatus() { + return versionStatus; + } + + public void setVersionStatus(DocumentVersionStatusType versionStatus) { + this.versionStatus = versionStatus; + } + + public List<DocumentVersionEventRO> getDocumentVersionEvents() { + if (documentVersionEvents == null) { + documentVersionEvents = new ArrayList<>(); + } + return documentVersionEvents; + } + + public OffsetDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(OffsetDateTime createdOn) { + this.createdOn = createdOn; + } + + public OffsetDateTime getLastUpdatedOn() { + return lastUpdatedOn; + } + + public void setLastUpdatedOn(OffsetDateTime lastUpdatedOn) { + this.lastUpdatedOn = lastUpdatedOn; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java index 369a5709e0fc410a064b8adafc3f49850223ab1e..aea12e5a96a5dc36a71701c1a6e6e7feca18b853 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/MemberRO.java @@ -28,6 +28,9 @@ public class MemberRO { MemberOfType memberOf; String fullName; MembershipRoleType roleType; + // Resource specific fields + Boolean hasPermissionReview; + public String getMemberId() { return memberId; @@ -65,6 +68,14 @@ public class MemberRO { return roleType; } + public Boolean getHasPermissionReview() { + return hasPermissionReview == null ? Boolean.FALSE : hasPermissionReview; + } + + public void setHasPermissionReview(Boolean hasPermissionReview) { + this.hasPermissionReview = hasPermissionReview; + } + public void setRoleType(MembershipRoleType roleType) { this.roleType = roleType; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ResourceRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ResourceRO.java index 5a91b3834385bd01cd80a2da30e6370580bc3395..52021bf363a96cada90c8e2a84732ab5522747d8 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ResourceRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ResourceRO.java @@ -42,6 +42,8 @@ public class ResourceRO extends BaseRO { private boolean smlRegistered = false; + private Boolean reviewEnabled; + private VisibilityType visibility = VisibilityType.PUBLIC; public String getResourceId() { @@ -84,6 +86,14 @@ public class ResourceRO extends BaseRO { this.smlRegistered = smlRegistered; } + public Boolean isReviewEnabled() { + return reviewEnabled; + } + + public void setReviewEnabled(Boolean reviewEnabled) { + this.reviewEnabled = reviewEnabled; + } + public VisibilityType getVisibility() { return visibility; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ReviewDocumentVersionRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ReviewDocumentVersionRO.java new file mode 100644 index 0000000000000000000000000000000000000000..9502fec65e2a1414f3c094c2cc13d97df2115ea5 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ReviewDocumentVersionRO.java @@ -0,0 +1,124 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; + +import java.time.OffsetDateTime; +/** + * Class represents RO for the document version for the review + * + * @since 5.1 + * @author Joze RIHARSIC + */ +public class ReviewDocumentVersionRO { + + private String documentId; + private String documentVersionId; + private String resourceId; + private String subresourceId; + private int version; + private DocumentVersionStatusType currentStatus = DocumentVersionStatusType.DRAFT; + private String resourceIdentifierValue; + private String resourceIdentifierScheme; + private String subresourceIdentifierValue; + private String subresourceIdentifierScheme; + private String target; + private OffsetDateTime lastUpdatedOn; + + + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + } + + public String getDocumentVersionId() { + return documentVersionId; + } + + public void setDocumentVersionId(String documentVersionId) { + this.documentVersionId = documentVersionId; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public DocumentVersionStatusType getCurrentStatus() { + return currentStatus; + } + + public void setCurrentStatus(DocumentVersionStatusType currentStatus) { + this.currentStatus = currentStatus; + } + + public String getSubresourceId() { + return subresourceId; + } + + public void setSubresourceId(String subresourceId) { + this.subresourceId = subresourceId; + } + + public String getResourceIdentifierValue() { + return resourceIdentifierValue; + } + + public void setResourceIdentifierValue(String resourceIdentifierValue) { + this.resourceIdentifierValue = resourceIdentifierValue; + } + + public String getResourceIdentifierScheme() { + return resourceIdentifierScheme; + } + + public void setResourceIdentifierScheme(String resourceIdentifierScheme) { + this.resourceIdentifierScheme = resourceIdentifierScheme; + } + + public String getSubresourceIdentifierValue() { + return subresourceIdentifierValue; + } + + public void setSubresourceIdentifierValue(String subresourceIdentifierValue) { + this.subresourceIdentifierValue = subresourceIdentifierValue; + } + + public String getSubresourceIdentifierScheme() { + return subresourceIdentifierScheme; + } + + public void setSubresourceIdentifierScheme(String subresourceIdentifierScheme) { + this.subresourceIdentifierScheme = subresourceIdentifierScheme; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public OffsetDateTime getLastUpdatedOn() { + return lastUpdatedOn; + } + + public void setLastUpdatedOn(OffsetDateTime lastUpdatedOn) { + this.lastUpdatedOn = lastUpdatedOn; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/DocumentVersionService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/DocumentVersionService.java new file mode 100644 index 0000000000000000000000000000000000000000..c3ba7d8e7fb5b479a9734d86e2fba7f3cb62523b --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/DocumentVersionService.java @@ -0,0 +1,155 @@ +package eu.europa.ec.edelivery.smp.services.resource; + +import eu.europa.ec.edelivery.smp.auth.SMPUserDetails; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; +import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; +import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersionEvent; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; + +/** + * Service for document version events + * + * @author Joze RIHTARSIC + * @since 5.1 + */ +@Service +public class DocumentVersionService { + protected static final SMPLogger LOG = SMPLoggerFactory.getLogger(DocumentVersionService.class); + public static final String DOCUMENT_VERSION_INITIALIZED_BY_GROUP_ADMIN = "Create and publish resource by group admin"; + + /** + * Create document version initialized by group admin. This is used when group admin creates and publishes resource. + * + * @param eventSourceType + * @return DBDocumentVersion initialized by group admin + */ + public DBDocumentVersion initializeDocumentVersionByGroupAdmin(EventSourceType eventSourceType) { + + return createDocumentVersionForCreate(eventSourceType, DOCUMENT_VERSION_INITIALIZED_BY_GROUP_ADMIN, true); + } + + + /** + * Create document version + * + * @param eventSourceType + * @param details + * @return + */ + public DBDocumentVersion createDocumentVersionForCreate(EventSourceType eventSourceType, + String details, boolean publish) { + + DBDocumentVersion dbDocumentVersion = new DBDocumentVersion(); + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.CREATE, eventSourceType, details); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.DRAFT); + if (publish) { + LOG.debug("Creating And Publish event for document version"); + publishDocumentVersion(dbDocumentVersion, eventSourceType); + } + return dbDocumentVersion; + } + + /** + * Method sets document version status to retired and adds retire event to the document version list of events + * + * @param dbDocumentVersion document version to be retired + * @param eventSourceType event source type + * @param details details of the event + */ + public void retireDocumentVersion(DBDocumentVersion dbDocumentVersion, EventSourceType eventSourceType, String details) { + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.RETIRE, eventSourceType, details); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.RETIRED); + } + + /** + * Method sets document version status to published and adds Publish event to the document version list of events + * + * @param dbDocumentVersion document version to be published + * @param eventSourceType event source type + */ + public void publishDocumentVersion(DBDocumentVersion dbDocumentVersion, EventSourceType eventSourceType) { + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.PUBLISH, eventSourceType, null); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.PUBLISHED); + } + + /** + * Method sets document version status to approved and add new event to list of events + * It also submits request mails to the review requesters + * + * @param dbDocumentVersion document version to be resource administrators + * @param eventSourceType event source type + * @param message message to be sent to the resource administrators + */ + public void rejectDocumentVersion(DBDocumentVersion dbDocumentVersion, EventSourceType eventSourceType, String message) { + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.REJECT, eventSourceType, message); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.REJECTED); + } + + /** + * Method sets document version status to approved and add new event to list of events + * It also submits request mails to the review requesters + * + * @param dbDocumentVersion document version to be resource administrators + * @param eventSourceType event source type + * @param message message to be sent to the resource administrators + */ + public void approveDocumentVersion(DBDocumentVersion dbDocumentVersion, EventSourceType eventSourceType, String message) { + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.APPROVE, eventSourceType, message); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.APPROVED); + } + + /** + * Method sets document version status to under_review and add new event to list of events + * It also submits request mails to the reviewers + * + * @param dbDocumentVersion document version to be reviewed + * @param eventSourceType event source type + */ + public void requestReviewDocumentVersion(DBDocumentVersion dbDocumentVersion, EventSourceType eventSourceType) { + DBDocumentVersionEvent dbEvent = createDocumentVersionEvent(DocumentVersionEventType.REQUEST_REVIEW, eventSourceType, null); + dbDocumentVersion.addNewDocumentVersionEvent(dbEvent); + dbDocumentVersion.setStatus(DocumentVersionStatusType.UNDER_REVIEW); + } + + + /** + * Create document version event + * + * @param eventType + * @param eventSourceType + * @param details + * @return + */ + public DBDocumentVersionEvent createDocumentVersionEvent(DocumentVersionEventType eventType, + EventSourceType eventSourceType, + String details) { + + SMPUserDetails userDetails = SessionSecurityUtils.getSessionUserDetails(); + DBDocumentVersionEvent dbEvent = new DBDocumentVersionEvent(); + + if (userDetails != null && userDetails.getUser() != null) { + dbEvent.setUsername(userDetails.getUser().getUsername()); + } else { + LOG.debug("User details not found for event creation "); + } + + dbEvent.setEventOn(OffsetDateTime.now()); + dbEvent.setEventType(eventType); + dbEvent.setEventSourceType(eventSourceType); + dbEvent.setDetails(details); + return dbEvent; + } + +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceHandlerService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceHandlerService.java index 50f35d88d0f5c6e244638631173c53eb0a6bf880..d4c8e3081342c07b54e05eede1dc4bee0f5a3bdb 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceHandlerService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceHandlerService.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -20,7 +20,9 @@ package eu.europa.ec.edelivery.smp.services.resource; import eu.europa.ec.edelivery.smp.data.dao.GroupDao; +import eu.europa.ec.edelivery.smp.data.dao.ResourceDao; import eu.europa.ec.edelivery.smp.data.dao.ResourceMemberDao; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBGroup; import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; @@ -67,16 +69,21 @@ public class ResourceHandlerService extends AbstractResourceHandler { final ResourceMemberDao resourceMemberDao; final GroupDao groupDao; final SMLIntegrationService integrationService; + final DocumentVersionService documentVersionService; + private final ResourceDao resourceDao; public ResourceHandlerService(List<ResourceDefinitionSpi> resourceDefinitionSpiList, ResourceMemberDao resourceMemberDao, GroupDao groupDao, ResourceStorage resourceStorage, - SMLIntegrationService integrationService) { + SMLIntegrationService integrationService, + DocumentVersionService documentVersionService, ResourceDao resourceDao) { super(resourceDefinitionSpiList, resourceStorage); this.resourceMemberDao = resourceMemberDao; this.groupDao = groupDao; this.integrationService = integrationService; + this.documentVersionService = documentVersionService; + this.resourceDao = resourceDao; } public void readResource(ResourceRequest resourceRequest, @@ -164,7 +171,7 @@ public class ResourceHandlerService extends AbstractResourceHandler { () -> resolvedData.getResourceDef().getMimeType())); } // create new document version - DBDocumentVersion documentVersion = new DBDocumentVersion(); + DBDocumentVersion documentVersion = documentVersionService.initializeDocumentVersionByGroupAdmin(EventSourceType.REST_API); documentVersion.setContent(baos.toByteArray()); DBResource managedResource = resourceStorage.addDocumentVersionForResource(resource, documentVersion); @@ -236,7 +243,7 @@ public class ResourceHandlerService extends AbstractResourceHandler { () -> resolvedData.getResourceDef().getMimeType())); } // create new document version - DBDocumentVersion documentVersion = new DBDocumentVersion(); + DBDocumentVersion documentVersion = documentVersionService.initializeDocumentVersionByGroupAdmin(EventSourceType.REST_API); documentVersion.setContent(baos.toByteArray()); resourceStorage.addDocumentVersionForSubresource(resolvedSubresource, documentVersion); diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java index 9d8aba290f9a907848f55aa96a07169e9c7f6ff9..f348d62c29b2ae4b7f319e4c3e2f2869c5ffea76 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceStorage.java @@ -22,6 +22,9 @@ package eu.europa.ec.edelivery.smp.services.resource; import eu.europa.ec.edelivery.smp.data.dao.DocumentDao; import eu.europa.ec.edelivery.smp.data.dao.ResourceDao; import eu.europa.ec.edelivery.smp.data.dao.SubresourceDao; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; import eu.europa.ec.edelivery.smp.data.model.doc.DBResource; @@ -51,11 +54,13 @@ public class ResourceStorage { final DocumentDao documentDao; final ResourceDao resourceDao; final SubresourceDao subresourceDao; + private final DocumentVersionService documentVersionService; - public ResourceStorage(DocumentDao documentDao, ResourceDao resourceDao, SubresourceDao subresourceDao) { + public ResourceStorage(DocumentDao documentDao, ResourceDao resourceDao, SubresourceDao subresourceDao, DocumentVersionService documentVersionService) { this.documentDao = documentDao; this.resourceDao = resourceDao; this.subresourceDao = subresourceDao; + this.documentVersionService = documentVersionService; } public byte[] getDocumentContentForResource(DBResource dbResource) { @@ -145,6 +150,13 @@ public class ResourceStorage { resource.setDocument(new DBDocument()); } DBResource managedResource = resource.getId() != null ? resourceDao.find(resource.getId()) : resourceDao.merge(resource); + DBDocument document = managedResource.getDocument(); + + // if document is not and alread have published version, retire it + document.getDocumentVersions().stream().filter(v -> v.getStatus() == DocumentVersionStatusType.PUBLISHED) + .forEach(documentVersion -> documentVersionService.retireDocumentVersion(documentVersion, EventSourceType.REST_API, null)); + + managedResource.getDocument().addNewDocumentVersion(version); return managedResource; } @@ -156,6 +168,12 @@ public class ResourceStorage { subresource.setDocument(new DBDocument()); } DBSubresource managedResource = subresource.getId() != null ? subresourceDao.find(subresource.getId()) : subresourceDao.merge(subresource); + DBDocument document = managedResource.getDocument(); + // retire existing published version + document.getDocumentVersions().stream() + .filter(v -> v.getStatus() == DocumentVersionStatusType.PUBLISHED) + .forEach(v -> {v.setStatus(DocumentVersionStatusType.RETIRED); + v.getDocumentVersionEvents().add(documentVersionService.createDocumentVersionEvent(DocumentVersionEventType.RETIRE, EventSourceType.REST_API, null));}); managedResource.getDocument().addNewDocumentVersion(version); return managedResource; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentService.java index fe70089bf84bc5c88b29143e2ec0e7f47ea43354..d992be9dfbd613ece82856091eb2413d7d27f16b 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentService.java @@ -22,32 +22,40 @@ import eu.europa.ec.edelivery.smp.config.enums.SMPPropertyTypeEnum; import eu.europa.ec.edelivery.smp.data.dao.DocumentDao; import eu.europa.ec.edelivery.smp.data.dao.ResourceDao; import eu.europa.ec.edelivery.smp.data.dao.SubresourceDao; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; import eu.europa.ec.edelivery.smp.data.model.DBDomainResourceDef; import eu.europa.ec.edelivery.smp.data.model.doc.*; import eu.europa.ec.edelivery.smp.data.model.ext.DBSubresourceDef; -import eu.europa.ec.edelivery.smp.data.ui.DocumentPropertyRO; -import eu.europa.ec.edelivery.smp.data.ui.DocumentRO; +import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; import eu.europa.ec.edelivery.smp.exceptions.ErrorCode; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.resource.DocumentVersionService; import eu.europa.ec.edelivery.smp.services.resource.ResourceHandlerService; import eu.europa.ec.edelivery.smp.services.spi.data.SpiResponseData; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; import eu.europa.ec.smp.spi.api.model.RequestData; import eu.europa.ec.smp.spi.api.model.ResponseData; import eu.europa.ec.smp.spi.enums.TransientDocumentPropertyType; import eu.europa.ec.smp.spi.exceptions.ResourceException; import eu.europa.ec.smp.spi.resource.ResourceHandlerSpi; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import static eu.europa.ec.smp.spi.enums.TransientDocumentPropertyType.*; @@ -55,16 +63,24 @@ import static eu.europa.ec.smp.spi.enums.TransientDocumentPropertyType.*; public class UIDocumentService { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UIDocumentService.class); - ResourceDao resourceDao; - SubresourceDao subresourceDao; - DocumentDao documentDao; - ResourceHandlerService resourceHandlerService; - - public UIDocumentService(ResourceDao resourceDao, SubresourceDao subresourceDao, DocumentDao documentDao, ResourceHandlerService resourceHandlerService) { + final ResourceDao resourceDao; + final SubresourceDao subresourceDao; + final DocumentDao documentDao; + final ResourceHandlerService resourceHandlerService; + final DocumentVersionService documentVersionService; + final ConversionService conversionService; + + public UIDocumentService(ResourceDao resourceDao, + SubresourceDao subresourceDao, + DocumentDao documentDao, + ResourceHandlerService resourceHandlerService, + DocumentVersionService documentVersionService, ConversionService conversionService) { this.resourceDao = resourceDao; this.subresourceDao = subresourceDao; this.documentDao = documentDao; this.resourceHandlerService = resourceHandlerService; + this.documentVersionService = documentVersionService; + this.conversionService = conversionService; } @Transactional @@ -94,29 +110,214 @@ public class UIDocumentService { } @Transactional - public DocumentRO generateDocumentForResource(Long resourceId, DocumentRO documentRo) { + public DocumentRO publishDocumentVersionForResource(Long resourceId, Long documentId, int version) { + LOG.info("Publish Document For Resource [{}], version [{}]", resourceId, version); + DBResource resource = resourceDao.find(resourceId); + DBDocument document = resource.getDocument(); + if (!Objects.equals(document.getId() ,documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the resource document id"); + } + return publishDocumentVersion(document, version, resource.isReviewEnabled(), getInitialProperties(resource)); + } + + @Transactional + public DocumentRO publishDocumentVersionForSubresource(Long subresourceId, Long resourceId, Long documentId, int version) { + LOG.info("Publish Document For subresource [{}], resource [{}], version [{}]", subresourceId, resourceId, version); + + DBSubresource subresource = subresourceDao.find(subresourceId); + DBResource resource = resourceDao.find(resourceId); + DBDocument document = subresource.getDocument(); + if (!Objects.equals(document.getId() ,documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the resource document id"); + } + return publishDocumentVersion(document, version, resource.isReviewEnabled(), getInitialProperties(subresource)); + } + + + private DocumentRO publishDocumentVersion(DBDocument document, int version, boolean isReviewEnabled, List<DocumentPropertyRO> initialProperties) { + + DBDocumentVersion documentVersion = document.getDocumentVersions().stream() + .filter(dv -> dv.getVersion() == version) + .findFirst().orElse(null); + if (documentVersion == null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentVersionNotFound", "Document version not found"); + } + if (isReviewEnabled && documentVersion.getStatus() != DocumentVersionStatusType.APPROVED) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentVersionAlreadyPublished", "Document version has wrong status"); + } + if (document.getDocumentVersions() != null && document.getCurrentVersion() == version) { + LOG.warn("Document version [{}] is already current version for the document [{}]", version, document.getId()); + return convertWithVersion(document, version, initialProperties); + } + //retire all other versions + document.getDocumentVersions().stream() + .filter(dv -> dv.getVersion() != version) + .filter(dv -> dv.getStatus() == DocumentVersionStatusType.PUBLISHED) + .forEach(dv -> documentVersionService.retireDocumentVersion(dv, EventSourceType.UI, "Retire document version")); + document.setCurrentVersion(documentVersion.getVersion()); + documentVersionService.publishDocumentVersion(documentVersion, EventSourceType.UI); + // return the document with the new version + return convertWithVersion(document, version, initialProperties); + } + + @Transactional + public DocumentRO requestReviewDocumentVersionForResource(Long resourceId, Long documentId, int version) { + LOG.info("Request review Document For Resource [{}], version [{}]", resourceId, version); + DBResource resource = resourceDao.find(resourceId); + DBDocument document = resource.getDocument(); + if (!Objects.equals(document.getId() ,documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the resource document id"); + } + return requestReviewDocumentVersion(document, version, resource.isReviewEnabled(), getInitialProperties(resource)); + } + + @Transactional + public DocumentRO requestReviewDocumentVersionForSubresource(Long subresourceId, Long resourceId, Long documentId, int version) { + LOG.info("Request review Document For subResource [{}], resource [{}], version [{}]", subresourceId, resourceId, version); + DBResource resource = resourceDao.find(resourceId); + DBSubresource subresource = subresourceDao.find(subresourceId); + DBDocument document = subresource.getDocument(); + if (!Objects.equals(document.getId(), documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the resource document id"); + } + return requestReviewDocumentVersion(document, version, resource.isReviewEnabled(), getInitialProperties(subresource)); + } + + + private DocumentRO requestReviewDocumentVersion(DBDocument document, int version, boolean isReviewEnabled, List<DocumentPropertyRO> initialProperties) { + DBDocumentVersion documentVersion = document.getDocumentVersions().stream() + .filter(dv -> dv.getVersion() == version) + .findFirst().orElse(null); + if (documentVersion == null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentVersionNotFound", "Document version not found"); + } + + if (!isReviewEnabled) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReviewNotEnabled", "Document Review is not enabled for the document"); + } + + if (documentVersion.getStatus() == DocumentVersionStatusType.PUBLISHED) { + LOG.warn("Document version [{}] request review action for document [{}] is not allowed. Wrong status", version, document.getId()); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReviewNotAllowed", "Document Review is not allowed for the document"); + } + + if (documentVersion.getStatus() == DocumentVersionStatusType.UNDER_REVIEW) { + LOG.warn("Document version review [{}] for document [{}] is already under review", version, document.getId()); + return convertWithVersion(document, version, initialProperties); + } + //retire all other versions + documentVersionService.requestReviewDocumentVersion(documentVersion, EventSourceType.UI); + // return the document with the new version + + return convertWithVersion(document, version, initialProperties); + } + + @Transactional + public DocumentRO reviewActionDocumentVersionForResource(Long resourceId, Long documentId, int version, DocumentVersionEventType action, String message) { + LOG.info("Approve review Document version For Resource [{}], version [{}]", resourceId, version); + DBResource resource = resourceDao.find(resourceId); + DBDocument document = resource.getDocument(); + if (!Objects.equals(document.getId(), documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the resource document id"); + } + return reviewActionDocumentVersion(document, version, resource.isReviewEnabled(), action, message, getInitialProperties(resource)); + } + + @Transactional + public DocumentRO reviewActionDocumentVersionForSubresource(Long subresourceId, Long resourceId, Long documentId, int version, DocumentVersionEventType action, String message) { + LOG.info("Approve review Document version For subResource [{}], resource [{}], version [{}]", subresourceId, resourceId, version); + DBResource resource = resourceDao.find(resourceId); + DBSubresource subresource = subresourceDao.find(subresourceId); + DBDocument document = subresource.getDocument(); + if (!Objects.equals(document.getId(), documentId)) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentIdMismatch", "Document id does not match the subresource document id"); + } + return reviewActionDocumentVersion(document, version, resource.isReviewEnabled(), action, message, getInitialProperties(subresource)); + } + + + private DocumentRO reviewActionDocumentVersion(DBDocument document, + int version, + boolean reviewEnabled, + DocumentVersionEventType action, + String message, + List<DocumentPropertyRO> initialProperties) { + + DBDocumentVersion documentVersion = document.getDocumentVersions().stream() + .filter(dv -> dv.getVersion() == version) + .findFirst() + .orElseThrow(() -> new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentVersionNotFound", "Document version not found")); + + if (!reviewEnabled) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReviewNotEnabled", "Document Review is not enabled for the document"); + } + + if (documentVersion.getStatus() != DocumentVersionStatusType.UNDER_REVIEW + && documentVersion.getStatus() != DocumentVersionStatusType.APPROVED) { + LOG.warn("Document version [{}] action for document [{}] not allowed. Wrong status", version, initialProperties); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReviewActionNotAllowed", "Document Review action is not allowed for the document"); + } + + if (action == DocumentVersionEventType.APPROVE) { + documentVersionService.approveDocumentVersion(documentVersion, EventSourceType.UI, message); + } else if (action == DocumentVersionEventType.REJECT) { + documentVersionService.rejectDocumentVersion(documentVersion, EventSourceType.UI, message); + } else { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReviewActionNotAllowed", "Document Review action is not allowed for the document"); + } + // return the document with the new version + return convertWithVersion(document, version, initialProperties); + } + + @Transactional + public DocumentRO generateDocumentForResource(Long resourceId) { LOG.info("generate Document For Resource"); DBResource resource = resourceDao.find(resourceId); + // generate document and write to output stream + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + generateDocumentForResource(resource, bos); + + String genDoc = bos.toString(); + DocumentRO result = new DocumentRO(); + result.setPayload(genDoc); + return result; + } + + public void generateDocumentForResource(DBResource resource, OutputStream outputStream) { + LOG.info("generate new Document For domainResourceDef"); DBDomainResourceDef domainResourceDef = resource.getDomainResourceDef(); + ResourceHandlerSpi resourceHandler = resourceHandlerService.getResourceHandler(domainResourceDef.getResourceDef()); RequestData data = resourceHandlerService.buildRequestDataForResource(domainResourceDef.getDomain(), resource, null); - return generateDocumentWithHandler(resourceHandler, data); + generateDocumentWithHandler(resourceHandler, data, outputStream); } @Transactional - public DocumentRO generateDocumentForSubresource(Long subresourceId, Long resourceId, DocumentRO documentRo) { - LOG.info("generate Document For Subresource"); + public DocumentRO generateDocumentForSubresource(Long subresourceId, Long resourceId) { + LOG.info("generate Document For Subresource identifier [{}]", subresourceId); DBResource parentEntity = resourceDao.find(resourceId); DBSubresource entity = subresourceDao.find(subresourceId); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + generateDocumentForSubresource(parentEntity, entity, bos); + + String genDoc = bos.toString(); + DocumentRO result = new DocumentRO(); + result.setPayload(genDoc); + return result; + } + + public void generateDocumentForSubresource(DBResource parentEntity, DBSubresource entity, OutputStream outputStream) { + LOG.info("generate Document For Subresource"); DBSubresourceDef subresourceDef = entity.getSubresourceDef(); ResourceHandlerSpi resourceHandler = resourceHandlerService.getSubresourceHandler(subresourceDef, subresourceDef.getResourceDef()); RequestData data = resourceHandlerService.buildRequestDataForSubResource(parentEntity.getDomainResourceDef().getDomain(), parentEntity, entity, null); - return generateDocumentWithHandler(resourceHandler, data); + generateDocumentWithHandler(resourceHandler, data, outputStream); } /** @@ -126,30 +327,28 @@ public class UIDocumentService { * @param data request data * @return DocumentRo with payload */ - private DocumentRO generateDocumentWithHandler(ResourceHandlerSpi resourceHandler, RequestData data) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ResponseData responseData = new SpiResponseData(bos); + private void generateDocumentWithHandler(ResourceHandlerSpi resourceHandler, RequestData data, OutputStream outputStream) { + + ResponseData responseData = new SpiResponseData(outputStream); try { resourceHandler.generateResource(data, responseData, Collections.emptyList()); } catch (ResourceException e) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "StoreResourceValidation", ExceptionUtils.getRootCauseMessage(e)); } - String genDoc = bos.toString(); - DocumentRO result = new DocumentRO(); - result.setPayload(genDoc); - return result; } @Transactional - public DocumentRO saveDocumentForResource(Long resourceId, DocumentRO documentRo) { + public DocumentRO saveDocumentForResource(Long resourceId, DocumentRO documentRo, Long documentReference) { final DBResource resource = resourceDao.find(resourceId); final DBDocument document = resource.getDocument(); + int returnDocVersion = document.getCurrentVersion(); boolean isPayloadChanged = documentRo.getPayloadStatus() != EntityROStatus.PERSISTED.getStatusNumber(); if (isPayloadChanged) { LOG.debug("Store resource payload for resource [{}]", resource.getIdentifierValue()); - storeResourcePayload(resource, document, documentRo); + DBDocumentVersion docVersion = storeResourcePayload(resource, document, documentRo); + returnDocVersion = docVersion.getVersion(); } if (isDocumentPropertiesChanged(documentRo)) { @@ -159,7 +358,33 @@ public class UIDocumentService { .forEach(p -> persistDocumentProperty(p, document)); } - return convertWithVersion(document, document.getCurrentVersion(), getInitialProperties(resource)); + if (Boolean.TRUE.equals(documentRo.getSharingEnabled()) && documentReference!=null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentSharingNotAllowed", "Document sharing is not allowed for the document with reference document"); + } + document.setSharingEnabled(documentRo.getSharingEnabled()); + + DBDocument documentReferenceEntity = null; + if (documentReference != null) { + if (Objects.equals(documentReference, document.getId())) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReferenceNotAllowed", "Document reference cannot be the same as the document id"); + } + documentReferenceEntity = documentDao.find(documentReference); + if (documentReferenceEntity == null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReferenceNotFound", "Document reference not found"); + } + + if (documentReferenceEntity.getSharingEnabled() != null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReferenceNotValid", "Can not reference to not shared document"); + } + + if (documentReferenceEntity.getReferenceDocument() != null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentReferenceNotValid", "Can not reference to a document that already has a reference"); + } + document.setReferenceDocument(documentReferenceEntity); + } else { + document.setReferenceDocument(null); + } + return convertWithVersion(document, returnDocVersion, getInitialProperties(resource)); } @@ -245,14 +470,16 @@ public class UIDocumentService { } /** - * Method stores the payload for the given resource as a new payload version. + * Method stores the payload for the given resource. If the resource has status New then new document version is created + * else the existing document version is updated with the new payload. + * <p> * The method invokes the ResourceHandlerSpi to update/validate the payload before storing it to database. * * @param resource resource to store the payload * @param document the resource database document entity * @param documentRo document RO the with new payload */ - private void storeResourcePayload(DBResource resource, DBDocument document, DocumentRO documentRo) { + private DBDocumentVersion storeResourcePayload(DBResource resource, DBDocument document, DocumentRO documentRo) { DBDomainResourceDef domainResourceDef = resource.getDomainResourceDef(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // invoke the resource handler for the document type @@ -266,18 +493,39 @@ public class UIDocumentService { } catch (ResourceException e) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "StoreResourceValidation", ExceptionUtils.getRootCauseMessage(e)); } + DBDocumentVersion documentVersion = null; + if (documentRo.getPayloadVersion() == null) { + documentVersion = createNewDocumentVersionForResource(resource, document, baos.toByteArray()); + } else { + documentVersion = document.getDocumentVersions().stream() + .filter(dv -> dv.getVersion() == documentRo.getPayloadVersion()) + .findFirst().orElse(null); + if (documentVersion == null) { + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "DocumentVersionNotFound", "Document version not found"); + } + documentVersion.setContent(baos.toByteArray()); + } + return documentVersion; + + } + + public DBDocumentVersion createNewDocumentVersionForResource(DBResource resource, DBDocument document, byte[] payload) { // create new version to document int version = document.getDocumentVersions().stream().mapToInt(DBDocumentVersion::getVersion) .max().orElse(0); - DBDocumentVersion documentVersion = new DBDocumentVersion(); + DBDocumentVersion documentVersion = documentVersionService.createDocumentVersionForCreate(EventSourceType.UI, "Create and publish resource by group admin", false); documentVersion.setVersion(version + 1); documentVersion.setDocument(document); - documentVersion.setContent(baos.toByteArray()); + documentVersion.setContent(payload); + documentVersion.setStatus(DocumentVersionStatusType.DRAFT); // to get the current persist time documentVersion.prePersist(); - document.getDocumentVersions().add(documentVersion); - document.setCurrentVersion(documentVersion.getVersion()); + document.getDocumentVersions().add(0, documentVersion); + if (Boolean.FALSE.equals(resource.isReviewEnabled())) { + document.setCurrentVersion(documentVersion.getVersion()); + } + return documentVersion; } @Transactional @@ -314,8 +562,6 @@ public class UIDocumentService { public DocumentRO getDocumentForResource(Long resourceId, int version) { DBResource resource = resourceDao.find(resourceId); DBDocument document = resource.getDocument(); - - return convertWithVersion(document, version, getInitialProperties(resource)); } @@ -365,7 +611,7 @@ public class UIDocumentService { // to get the current persist time documentVersion.prePersist(); - document.getDocumentVersions().add(documentVersion); + document.getDocumentVersions().add(0, documentVersion); document.setCurrentVersion(documentVersion.getVersion()); return convert(document, documentVersion, initialProperties); } @@ -388,18 +634,34 @@ public class UIDocumentService { return convert(document, documentVersion, initialProperties); } + + /** + * Convert DBDocument to DocumentRo with given document version + * + * @param document to convert to DocumentRo + * @param version to set as version + * @param initialProperties + * @return + */ public DocumentRO convert(DBDocument document, DBDocumentVersion version, List<DocumentPropertyRO> initialProperties) { DocumentRO documentRo = new DocumentRO(); documentRo.addProperty(DOCUMENT_NAME.getPropertyName(), document.getName(), "Document Name", SMPPropertyTypeEnum.STRING, true); documentRo.addProperty(DOCUMENT_MIMETYPE.getPropertyName(), document.getMimeType(), "Document Mimetype", SMPPropertyTypeEnum.STRING, true); - + documentRo.setSharingEnabled(document.getSharingEnabled()); documentRo.getProperties().addAll(initialProperties); // set list of versions - document.getDocumentVersions().forEach(dv -> - documentRo.getAllVersions().add(dv.getVersion())); + document.getDocumentVersions().forEach(dv -> { + documentRo.getAllVersions().add(dv.getVersion()); + documentRo.getDocumentVersions().add(conversionService.convert(dv, DocumentVersionRO.class)); + }); + + documentRo.setDocumentId(SessionSecurityUtils.encryptedEntityId(document.getId())); + if (document.getReferenceDocument() != null) { + documentRo.setReferenceDocumentId(SessionSecurityUtils.encryptedEntityId(document.getReferenceDocument().getId())); + } documentRo.setMimeType(document.getMimeType()); documentRo.setName(document.getName()); @@ -418,8 +680,52 @@ public class UIDocumentService { documentRo.setPayloadCreatedOn(version.getCreatedOn()); documentRo.setPayloadVersion(version.getVersion()); documentRo.setPayload(new String(version.getContent())); + documentRo.setDocumentVersionStatus(version.getStatus()); + // set ven + version.getDocumentVersionEvents().stream().forEach(e -> { + DocumentVersionEventRO eventRo = new DocumentVersionEventRO(); + eventRo.setEventType(e.getEventType()); + eventRo.setEventOn(e.getEventOn()); + eventRo.setUsername(e.getUsername()); + eventRo.setEventSourceType(e.getEventSourceType()); + eventRo.setDetails(e.getDetails()); + documentRo.addDocumentVersionEvent(eventRo); + }); } return documentRo; } + + + public ServiceResult<ReviewDocumentVersionRO> getDocumentReviewListForUser( + Long userId, + int page, int pageSize, + String sortField, + String sortOrder, Object filter) { + + ServiceResult<ReviewDocumentVersionRO> sg = new ServiceResult<>(); + sg.setPage(page < 0 ? 0 : page); + List<DBReviewDocumentVersion> listTask = documentDao.getDocumentReviewListForUser(userId); + long iCnt = listTask.size(); + + if (pageSize < 0) { // if page size iz -1 return all results and set pageSize to maxCount + pageSize = (int) iCnt; + } + sg.setPageSize(pageSize); + sg.setCount(iCnt); + + if (iCnt > 0) { + int iStartIndex = pageSize < 0 ? -1 : page * pageSize; + if (iStartIndex >= iCnt && page > 0) { + page = page - 1; + sg.setPage(page); // go back for a page + iStartIndex = pageSize < 0 ? -1 : page * pageSize; + } + } + + List<ReviewDocumentVersionRO> result = listTask.stream().map(resource -> conversionService.convert(resource, ReviewDocumentVersionRO.class)) + .collect(Collectors.toList()); + sg.getServiceEntities().addAll(result); + return sg; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java index b08214f1b43870f6d8aa76bfbcb1d62b3aa143d7..7441f3305774c621f2fefda9d43af6ac7ea2bee4 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIResourceService.java @@ -8,9 +8,9 @@ * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: - * + * * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and limitations under the Licence. @@ -18,13 +18,15 @@ */ package eu.europa.ec.edelivery.smp.services.ui; -import eu.europa.ec.edelivery.smp.services.IdentifierService; import eu.europa.ec.edelivery.smp.data.dao.*; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBDomainResourceDef; import eu.europa.ec.edelivery.smp.data.model.DBGroup; import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; +import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; import eu.europa.ec.edelivery.smp.data.model.doc.DBResource; import eu.europa.ec.edelivery.smp.data.model.doc.DBResourceFilter; import eu.europa.ec.edelivery.smp.data.model.ext.DBResourceDef; @@ -38,19 +40,23 @@ import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.identifiers.Identifier; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.IdentifierService; import eu.europa.ec.edelivery.smp.services.SMLIntegrationService; +import eu.europa.ec.edelivery.smp.services.resource.DocumentVersionService; import org.apache.commons.lang3.StringUtils; import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.ByteArrayOutputStream; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + /** - * * @author Joze Rihtarsic * @since 5.0 */ @@ -75,13 +81,16 @@ public class UIResourceService { private final IdentifierService identifierService; private final ConversionService conversionService; private final SMLIntegrationService smlIntegrationService; + private final UIDocumentService uiDocumentService; + private final DocumentVersionService documentVersionService; public UIResourceService(ResourceDao resourceDao, ResourceMemberDao resourceMemberDao, ResourceDefDao resourceDefDao, DomainResourceDefDao domainResourceDefDao, UserDao userDao, GroupDao groupDao, IdentifierService identifierService, ConversionService conversionService, - SMLIntegrationService smlIntegrationService) { + SMLIntegrationService smlIntegrationService, + UIDocumentService uiDocumentService, DocumentVersionService documentVersionService) { this.resourceDao = resourceDao; this.resourceMemberDao = resourceMemberDao; this.resourceDefDao = resourceDefDao; @@ -91,6 +100,8 @@ public class UIResourceService { this.identifierService = identifierService; this.conversionService = conversionService; this.smlIntegrationService = smlIntegrationService; + this.uiDocumentService = uiDocumentService; + this.documentVersionService = documentVersionService; } @@ -171,7 +182,7 @@ public class UIResourceService { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_DELETE, "Resource does not belong to the group!"); } if (!Objects.equals(resource.getGroup().getDomain().getId(), domainId)) { - throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_CREATE, "Group does not belong to the given domain!"); + throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_DELETE, "Group does not belong to the given domain!"); } DBDomain resourceDomain = resource.getGroup().getDomain(); if (smlIntegrationService.isSMLIntegrationEnabled() && @@ -210,19 +221,20 @@ public class UIResourceService { resourceRO.getIdentifierScheme(), resourceRO.getIdentifierValue()); - Optional<DBResource> existResource = resourceDao.getResource(resourceIdentifier.getValue(),resourceIdentifier.getScheme(), optRedef.get(), group.getDomain()); + Optional<DBResource> existResource = resourceDao.getResource(resourceIdentifier.getValue(), resourceIdentifier.getScheme(), optRedef.get(), group.getDomain()); if (existResource.isPresent()) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_CREATE, "Resource [val:" + resourceRO.getIdentifierValue() + " scheme:" + resourceRO.getIdentifierScheme() + "] already exists for domain!"); } - DBResource resource = new DBResource(); resource.setIdentifierScheme(resourceIdentifier.getScheme()); resource.setIdentifierValue(resourceIdentifier.getValue()); resource.setVisibility(resourceRO.getVisibility()); resource.setGroup(group); resource.setDomainResourceDef(optDoredef.get()); - DBDocument document = createDocumentForResourceDef(optRedef.get()); + resource.setReviewEnabled(resourceRO.isReviewEnabled()); + + DBDocument document = createDocumentForNewResource(resource); resource.setDocument(document); resourceDao.persist(resource); // create first member as admin user @@ -242,6 +254,15 @@ public class UIResourceService { return conversionService.convert(resource, ResourceRO.class); } + /** + * Method allows Group admin and Resource admin to change resource visibility and enable/disable review flow. + * + * @param resourceRO + * @param resourceId + * @param groupId + * @param domainId + * @return + */ @Transactional public ResourceRO updateResourceForGroup(ResourceRO resourceRO, Long resourceId, Long groupId, Long domainId) { @@ -264,9 +285,13 @@ public class UIResourceService { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, ACTION_RESOURCE_UPDATE, "Resource definition [" + resourceRO.getResourceTypeIdentifier() + "] is not registered for domain!"); } - // at the moment only visibility can be updated for the resource + // at the moment only visibility and review enabled + // can be updated for the resource DBResource resource = resourceDao.find(resourceId); resource.setVisibility(resourceRO.getVisibility()); + if (resourceRO.isReviewEnabled() != null) { + resource.setReviewEnabled(isTrue(resourceRO.isReviewEnabled())); + } return conversionService.convert(resource, ResourceRO.class); } @@ -295,9 +320,9 @@ public class UIResourceService { * Add or update a member to a resource * * @param resourceId resource id to add member to - * @param groupId group id to add member to - * @param memberRO member data - * @param memberId member id (optional) if null then add member, if not null then update member + * @param groupId group id to add member to + * @param memberRO member data + * @param memberId member id (optional) if null then add member, if not null then update member * @return added member RO */ @Transactional @@ -312,12 +337,16 @@ public class UIResourceService { if (memberId != null) { member = resourceMemberDao.find(memberId); member.setRole(memberRO.getRoleType()); + member.setHasPermissionToReview(memberRO.getHasPermissionReview()); } else { DBResource resource = resourceDao.find(resourceId); if (resourceMemberDao.isUserResourceMember(user, resource)) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "Add membership", "User [" + memberRO.getUsername() + "] is already a member!"); } - member = resourceMemberDao.addMemberToResource(resource, user, memberRO.getRoleType()); + member = resourceMemberDao.addMemberToResource(resource, user, + memberRO.getRoleType(), + isTrue(memberRO.getHasPermissionReview()) + ); } return conversionService.convert(member, MemberRO.class); } @@ -349,11 +378,31 @@ public class UIResourceService { return resource; } - public DBDocument createDocumentForResourceDef(DBResourceDef resourceDef) { + /** + * Create document for new resource. Method is called when GroupAdmin creates new resource via + * UI. + * + * @param resource resource to create document for + * @return created document + */ + public DBDocument createDocumentForNewResource(DBResource resource) { + DBResourceDef domainResourceDef = resource.getDomainResourceDef().getResourceDef(); DBDocument document = new DBDocument(); + document.setCurrentVersion(1); - document.setMimeType(resourceDef.getMimeType()); - document.setName(resourceDef.getName()); + document.setMimeType(domainResourceDef.getMimeType()); + document.setName(domainResourceDef.getName()); + // create first version of the document + DBDocumentVersion version = documentVersionService.initializeDocumentVersionByGroupAdmin(EventSourceType.UI); + // The first version is always published. + version.setStatus(DocumentVersionStatusType.PUBLISHED); + version.setDocument(document); + version.setVersion(1); + // generate document content + document.addNewDocumentVersion(version); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + uiDocumentService.generateDocumentForResource(resource, baos); + version.setContent(baos.toByteArray()); return document; } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UISubresourceService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UISubresourceService.java index e5e201640835e76093ef040734eb4052e4fc67ee..83463021ab0da5b29380b0d8853e5cbd2dba408f 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UISubresourceService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UISubresourceService.java @@ -18,7 +18,10 @@ */ package eu.europa.ec.edelivery.smp.services.ui; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.EventSourceType; import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; import eu.europa.ec.edelivery.smp.services.IdentifierService; import eu.europa.ec.edelivery.smp.data.dao.ResourceDao; import eu.europa.ec.edelivery.smp.data.dao.SubresourceDao; @@ -33,10 +36,12 @@ import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.identifiers.Identifier; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.resource.DocumentVersionService; import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.ByteArrayOutputStream; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -53,26 +58,26 @@ public class UISubresourceService { private static final String ACTION_SUBRESOURCE_CREATE = "CreateSubresourceForResource"; private static final String ACTION_SUBRESOURCE_DELETE = "DeleteSubresourceFromResource"; - private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UISubresourceService.class); - private final SubresourceDao subresourceDao; - private final ResourceDao resourceDao; private final SubresourceDefDao subresourceDefDao; - - private final IdentifierService identifierService; - - + private final DocumentVersionService documentVersionService; + private final UIDocumentService uiDocumentService; private final ConversionService conversionService; public UISubresourceService(SubresourceDao subresourceDao, ResourceDao resourceDao,SubresourceDefDao subresourceDefDao, IdentifierService identifierService, - ConversionService conversionService) { + ConversionService conversionService, + DocumentVersionService documentVersionService, + UIDocumentService uiDocumentService + ) { this.subresourceDao = subresourceDao; this.resourceDao = resourceDao; this.subresourceDefDao = subresourceDefDao; this.identifierService = identifierService; this.conversionService = conversionService; + this.documentVersionService = documentVersionService; + this.uiDocumentService = uiDocumentService; } @@ -125,18 +130,31 @@ public class UISubresourceService { subresource.setIdentifierValue(docId.getValue()); subresource.setResource(resParent); subresource.setSubresourceDef(optRedef.get()); - DBDocument document = createDocumentForSubresourceDef(optRedef.get()); + DBDocument document = createDocumentForSubresourceDef(optRedef.get(), subresource, resParent); subresource.setDocument(document); subresourceDao.persist(subresource); // create first member as admin user return conversionService.convert(subresource, SubresourceRO.class); } - public DBDocument createDocumentForSubresourceDef(DBSubresourceDef subresourceDef) { + public DBDocument createDocumentForSubresourceDef(DBSubresourceDef subresourceDef, DBSubresource subresource, DBResource resource) { DBDocument document = new DBDocument(); document.setCurrentVersion(1); document.setMimeType(subresourceDef.getMimeType()); document.setName(subresourceDef.getName()); + + + // create first version of the document + DBDocumentVersion version = documentVersionService.initializeDocumentVersionByGroupAdmin(EventSourceType.UI); + + version.setStatus(DocumentVersionStatusType.PUBLISHED); + version.setDocument(document); + version.setVersion(1); + // generate document content + document.addNewDocumentVersion(version); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + uiDocumentService.generateDocumentForSubresource(resource,subresource, baos); + version.setContent(baos.toByteArray()); return document; } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDaoTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDaoTest.java index 66e0beceafb33dbd77aff3deb9b49dcf2039ef14..d595707da01b981542f1ddaf92342cac6f6afb1f 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDaoTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/DocumentDaoTest.java @@ -18,11 +18,14 @@ */ package eu.europa.ec.edelivery.smp.data.dao; -import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument; -import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentProperty; -import eu.europa.ec.edelivery.smp.data.model.doc.DBDocumentVersion; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionStatusType; +import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; +import eu.europa.ec.edelivery.smp.data.enums.VisibilityType; +import eu.europa.ec.edelivery.smp.data.model.doc.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; import javax.transaction.Transactional; @@ -39,6 +42,7 @@ class DocumentDaoTest extends AbstractBaseDao { @BeforeEach public void prepareDatabase() { testUtilsDao.clearData(); + testUtilsDao.createResourceMemberships(); testUtilsDao.createSubresources(); } @@ -79,7 +83,8 @@ class DocumentDaoTest extends AbstractBaseDao { assertTrue(result.isPresent()); // the default setup createResources sets two versions (0 and 1 ) with current version 1 assertEquals(2, result.get().getVersion()); - assertEquals(testUtilsDao.getDocumentD1G1RD1().getDocumentVersions().get(1), result.get()); + // note that the versions are ordered by version desc + assertEquals(testUtilsDao.getDocumentD1G1RD1().getDocumentVersions().get(0), result.get()); } @@ -98,7 +103,8 @@ class DocumentDaoTest extends AbstractBaseDao { assertTrue(result.isPresent()); // the default setup createResources sets two versions (1 and 2 ) with current version 2 assertEquals(2, result.get().getVersion()); - assertEquals(testUtilsDao.getDocumentD1G1RD1_S1().getDocumentVersions().get(1), result.get()); + // note that the versions are ordered by version desc + assertEquals(testUtilsDao.getDocumentD1G1RD1_S1().getDocumentVersions().get(0), result.get()); } @Test @@ -120,4 +126,41 @@ class DocumentDaoTest extends AbstractBaseDao { assertEquals(2, result.getDocumentProperties().size()); assertEquals(property1, result.getDocumentProperties().get(0)); } + + @Test + void testPersistUnderReviewDocument() { + DBResource createResourceWithStatusReview = testUtilsDao.createResource("review", "1-1-1", VisibilityType.PUBLIC, + DocumentVersionStatusType.UNDER_REVIEW, + testUtilsDao.getDomainResourceDefD1R1(), testUtilsDao.getGroupD1G1()); + + testUtilsDao.createResourceMembership(MembershipRoleType.ADMIN, testUtilsDao.getUser1(), createResourceWithStatusReview, true); + + List<DBReviewDocumentVersion> dbDocumentVersions = testInstance.getDocumentReviewListForUser(testUtilsDao.getUser1().getId()); + assertEquals(1, dbDocumentVersions.size()); + + } + + @ParameterizedTest + @CsvSource({ + "UNDER_REVIEW, UNDER_REVIEW, 2", + "UNDER_REVIEW, DRAFT, 1", + "DRAFT, UNDER_REVIEW, 1", + "DRAFT, DRAFT, 0" + }) + void testPersistUnderReviewDocumentSubresource(String resourceStatus, String subresourceStatus, int expectedSize) { + testUtilsDao.createResourceDefinitions(); + DBResource resource = testUtilsDao.createResource("review", "1-1-1", VisibilityType.PUBLIC, + DocumentVersionStatusType.valueOf(resourceStatus), + testUtilsDao.getDomainResourceDefD1R1(), testUtilsDao.getGroupD1G1()); + + DBSubresource subres = testUtilsDao.createSubresource(resource, "1-1-1", "1-1-1", + DocumentVersionStatusType.valueOf(subresourceStatus), + testUtilsDao.getSubresourceDefSmpMetadata()); + + testUtilsDao.createResourceMembership(MembershipRoleType.ADMIN, testUtilsDao.getUser1(), resource, true); + + List<DBReviewDocumentVersion> dbDocumentVersions = testInstance.getDocumentReviewListForUser(testUtilsDao.getUser1().getId()); + assertEquals(expectedSize, dbDocumentVersions.size()); + + } } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java index 7eba22bdaefc782ae263f90a86061c34979e268c..e96d569a97dccfb84246ea8a1d69d3ced2f5bc85 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java @@ -19,10 +19,7 @@ package eu.europa.ec.edelivery.smp.data.dao; import eu.europa.ec.edelivery.smp.config.enums.SMPDomainPropertyEnum; -import eu.europa.ec.edelivery.smp.data.enums.CredentialTargetType; -import eu.europa.ec.edelivery.smp.data.enums.CredentialType; -import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType; -import eu.europa.ec.edelivery.smp.data.enums.VisibilityType; +import eu.europa.ec.edelivery.smp.data.enums.*; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBDomainConfiguration; import eu.europa.ec.edelivery.smp.data.model.DBDomainResourceDef; @@ -109,8 +106,6 @@ public class TestUtilsDao { DBResourceMember resourceMemberU1R2_D2G1RD1_Viewer; DBResource resourcePrivateD1G1RD1; - // DBResource resourceInternalD1G1RD1; - DBExtension extension; boolean searchDataCreated = false; @@ -152,7 +147,6 @@ public class TestUtilsDao { resourceMemberU1R2_D2G1RD1_Viewer = null; resourcePrivateD1G1RD1 = null; - //resourceInternalD1G1RD1 = null; extension = null; searchDataCreated = false; @@ -410,9 +404,15 @@ public class TestUtilsDao { @Transactional public DBResourceMember createResourceMembership(MembershipRoleType roleType, DBUser user, DBResource resource){ + return createResourceMembership(roleType, user, resource, false); + } + + @Transactional + public DBResourceMember createResourceMembership(MembershipRoleType roleType, DBUser user, DBResource resource, boolean hasPermissionToReview){ DBResourceMember member = new DBResourceMember(); member.setRole(roleType); member.setUser(user); + member.setHasPermissionToReview(hasPermissionToReview); member.setResource(resource); persistFlushDetach(member); assertNotNull(member.getId()); @@ -457,18 +457,58 @@ public class TestUtilsDao { } @Transactional - public DBResource createResource(String identifier, String schema, VisibilityType visibilityType, DBDomainResourceDef domainResourceDef, DBGroup group) { + public DBResource createResource(String identifier, String schema, + VisibilityType visibilityType, + DBDomainResourceDef domainResourceDef, + DBGroup group) { + + return createResource(identifier, schema, visibilityType, DocumentVersionStatusType.PUBLISHED, domainResourceDef, group); + } + + @Transactional + public DBResource createResource(String identifier, String schema, + VisibilityType visibilityType, + DocumentVersionStatusType status, + DBDomainResourceDef domainResourceDef, + DBGroup group) { - DBResource resource = TestDBUtils.createDBResource(identifier, schema); + DBResource resource = TestDBUtils.createDBResource(identifier, schema, true, status); resource.setVisibility(visibilityType); resource.setGroup(group); resource.setDomainResourceDef(domainResourceDef); + resource.setReviewEnabled(true); persistFlushDetach(resource); assertNotNull(resource.getId()); return resource; } + @Transactional + public DBSubresource createSubresource(DBResource resource, String identifier, String schema, + DocumentVersionStatusType status, DBSubresourceDef subresourceDefSmp) { + + DBSubresource dbSubresource = TestDBUtils.createDBSubresource( + resource.getIdentifierValue(),resource.getIdentifierScheme(), + identifier, schema); + + + dbSubresource.setSubresourceDef(subresourceDefSmp); + + DBDocument doc = createDocument(1, resourceD1G1RD1.getIdentifierValue(), resourceD1G1RD1.getIdentifierScheme(), + identifier, schema); + doc.getDocumentVersions().get(0).setStatus(status); + + dbSubresource.setDocument(doc); + dbSubresource.setResource(resource); + + + persistFlushDetach(dbSubresource); + assertNotNull(dbSubresource.getId()); + return dbSubresource; + } + + + /** * Create resources with subresources for ids: diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java index 89425ad0d67b2410c195da88d00ac9f56d17b273..61dfed3fc8329b36c3e57a55432ee9bddad5b1c5 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIDocumentServiceTest.java @@ -68,7 +68,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { @Test void testGenerateDocumentForResource() { - DocumentRO result = testInstance.generateDocumentForResource(testUtilsDao.getResourceD1G1RD1().getId(), null); + DocumentRO result = testInstance.generateDocumentForResource(testUtilsDao.getResourceD1G1RD1().getId()); assertNotNull(result); assertNotNull(result.getPayload()); } @@ -78,8 +78,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { DBSubresource subresource = testUtilsDao.getSubresourceD1G1RD1_S1(); DocumentRO result = testInstance.generateDocumentForSubresource(subresource.getId(), - subresource.getResource().getId(), - null); + subresource.getResource().getId()); assertNotNull(result); assertNotNull(result.getPayload()); } @@ -87,7 +86,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { @Test void testValidateForResource() { DBResource resource = testUtilsDao.getResourceD1G1RD1(); - DocumentRO testDoc = testInstance.generateDocumentForResource(resource.getId(), null); + DocumentRO testDoc = testInstance.generateDocumentForResource(resource.getId()); assertNotNull(testDoc.getPayload()); // must not throw exception testInstance.validateDocumentForResource(resource.getId(), testDoc); @@ -110,8 +109,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { void testValidateForSubresource() { DBSubresource subresource = testUtilsDao.getSubresourceD1G1RD1_S1(); DocumentRO testDoc = testInstance.generateDocumentForSubresource(subresource.getId(), - subresource.getResource().getId(), - null); + subresource.getResource().getId()); assertNotNull(testDoc.getPayload()); // must not throw exception @@ -147,10 +145,10 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { @Test void testSaveDocumentForResource() { DBResource resource = testUtilsDao.getResourceD1G1RD1(); - DocumentRO testDoc = testInstance.generateDocumentForResource(resource.getId(), null); + DocumentRO testDoc = testInstance.generateDocumentForResource(resource.getId()); assertNotNull(testDoc.getPayload()); //when - DocumentRO result = testInstance.saveDocumentForResource(resource.getId(), testDoc); + DocumentRO result = testInstance.saveDocumentForResource(resource.getId(), testDoc, null); // then assertNotNull(result); } @@ -159,8 +157,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { void testSaveDocumentForSubresource() { DBSubresource subresource = testUtilsDao.getSubresourceD1G1RD1_S1(); DocumentRO testDoc = testInstance.generateDocumentForSubresource(subresource.getId(), - subresource.getResource().getId(), - null); + subresource.getResource().getId()); assertNotNull(testDoc.getPayload()); //when @@ -173,8 +170,7 @@ public class UIDocumentServiceTest extends AbstractServiceIntegrationTest { void testTransientResolutionForSubresourceDocument() { DBSubresource subresource = testUtilsDao.getSubresourceD1G1RD1_S1(); DocumentRO testDoc = testInstance.generateDocumentForSubresource(subresource.getId(), - subresource.getResource().getId(), - null); + subresource.getResource().getId()); assertNotNull(testDoc.getPayload()); // extension used by this test is SMP example extension which generates document with placeholders Assertions.assertThat(testDoc.getPayload()).contains(TransientDocumentPropertyType.RESOURCE_IDENTIFIER_VALUE.getPropertyPlaceholder()); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorDomainTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorDomainTest.java index 11bba96179669989f77ca3d6f1f138f547eaf8ed..ce99037235b8f32afa72d2cbf9d026bada7a1feb 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorDomainTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorDomainTest.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java index 5711a5601b5145bf53f0924f8e934445912088f2..3267be90acd379d713e52fe8152aacd3ba2fd4e9 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java @@ -18,10 +18,7 @@ */ package eu.europa.ec.edelivery.smp.testutil; -import eu.europa.ec.edelivery.smp.data.enums.ApplicationRoleType; -import eu.europa.ec.edelivery.smp.data.enums.CredentialTargetType; -import eu.europa.ec.edelivery.smp.data.enums.CredentialType; -import eu.europa.ec.edelivery.smp.data.enums.VisibilityType; +import eu.europa.ec.edelivery.smp.data.enums.*; import eu.europa.ec.edelivery.smp.data.model.DBAlert; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBGroup; @@ -170,22 +167,26 @@ public class TestDBUtils { return createDBResource(id, sch, true); } - - public static DBResource createDBResource(String id, String sch, boolean withExtension) { + public static DBResource createDBResource(String id, String sch, boolean withExtension, + DocumentVersionStatusType statusType) { DBResource resource = new DBResource(); resource.setIdentifierValue(id); resource.setIdentifierScheme(sch); resource.setVisibility(VisibilityType.PUBLIC); if (withExtension) { DBDocument document = createDBDocument(); - DBDocumentVersion documentVersion = createDBDocumentVersion(id, sch); - createDBDocumentVersion(id, sch).setContent(generateExtension()); + DBDocumentVersion documentVersion = createDBDocumentVersion(id, sch, statusType); document.addNewDocumentVersion(documentVersion); resource.setDocument(document); } return resource; } + + public static DBResource createDBResource(String id, String sch, boolean withExtension) { + return createDBResource(id, sch, withExtension, DocumentVersionStatusType.DRAFT); + } + public static DBDocument createDBDocument() { DBDocument doc = new DBDocument(); doc.setMimeType("application/xml"); @@ -194,7 +195,12 @@ public class TestDBUtils { } public static DBDocumentVersion createDBDocumentVersion(String id, String sch) { + return createDBDocumentVersion(id, sch, DocumentVersionStatusType.DRAFT); + } + + public static DBDocumentVersion createDBDocumentVersion(String id, String sch, DocumentVersionStatusType status) { DBDocumentVersion docuVersion = new DBDocumentVersion(); + docuVersion.setStatus(status); docuVersion.setContent(("<ServiceGroup xmlns=\"http://docs.oasis-open.org/bdxr/ns/SMP/2016/05\">" + "<ParticipantIdentifier scheme=\"" + sch + "\">" + id + "</ParticipantIdentifier>" + "<ServiceMetadataReferenceCollection />" + diff --git a/smp-server-library/src/test/resources/cleanup-database.sql b/smp-server-library/src/test/resources/cleanup-database.sql index 847d25bd63fd55ac7d4b466f84a4861211b80bb2..ea5f8adc8321046e86f48b9fcb0f9c569ae457bc 100755 --- a/smp-server-library/src/test/resources/cleanup-database.sql +++ b/smp-server-library/src/test/resources/cleanup-database.sql @@ -18,6 +18,7 @@ DELETE FROM SMP_SUBRESOURCE; DELETE FROM SMP_SUBRESOURCE_AUD; DELETE FROM SMP_RESOURCE; DELETE FROM SMP_RESOURCE_AUD; +DELETE FROM SMP_DOCUMENT_VERSION_EVENT; DELETE FROM SMP_DOCUMENT_PROPERTY; DELETE FROM SMP_DOCUMENT_PROPERTY_AUD; DELETE FROM SMP_DOCUMENT_VERSION; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java index a93941668f529b9659d3d7e5881579041083619a..ee11d19736be8172904d8cd4450a745180e28f92 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthorizationService.java @@ -114,6 +114,12 @@ public class SMPAuthorizationService { return resourceMemberDao.isUserResourceMemberWithRole(userDetails.getUser().getId(), resourceId, MembershipRoleType.ADMIN); } + public boolean isResourceReviewer(String resourceEncId) { + SMPUserDetails userDetails = getAndValidateUserDetails(); + Long resourceId = getIdFromEncryptedString(resourceEncId, false); + return resourceMemberDao.isUserResourceMemberWithReviewPermission(userDetails.getUser().getId(), resourceId); + } + public boolean isResourceMember(String resourceEncId) { SMPUserDetails userDetails = getAndValidateUserDetails(); Long resourceId = getIdFromEncryptedString(resourceEncId, false); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java index 17577ff99f661a6bd3ebc8bd0fc6326129b584a6..7f114ab42bfa7cbaf1a38325eff69d7ed7001d4f 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/error/AbstractErrorControllerAdvice.java @@ -52,7 +52,7 @@ abstract class AbstractErrorControllerAdvice { response = buildAndLog(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); }else if (runtimeException instanceof AccessDeniedException){ AccessDeniedException ex = (AccessDeniedException)runtimeException; - response = buildAndLog(UNAUTHORIZED, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); + response = buildAndLog(FORBIDDEN, ErrorBusinessCode.UNAUTHORIZED, ex.getMessage(), ex); }else if (runtimeException instanceof BadRequestException){ BadRequestException ex = (BadRequestException)runtimeException; response = buildAndLog(UNPROCESSABLE_ENTITY, ex.getErrorBusinessCode(), ex.getMessage(), ex); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java index 6c94d2e7507d049d850f4d0635dd60d13eb983dc..77f20f6dfc426355588b189bb94ab6c578e008cc 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java @@ -46,6 +46,7 @@ public class ResourceConstants { public static final String PATH_RESOURCE_TYPE_SUBRESOURCE = "subresource"; public static final String PATH_RESOURCE_TYPE_DOCUMENT = "document"; public static final String PATH_RESOURCE_TYPE_PROPERTY = "property"; + public static final String PATH_RESOURCE_TYPE_REVIEW = "review-task"; public static final String PATH_RESOURCE_TYPE_RESOURCE_DEFINITION = "res-def"; /** @@ -70,6 +71,11 @@ public class ResourceConstants { public static final String PATH_ACTION_PUT = "put"; public static final String PATH_ACTION_VALIDATE = "validate"; public static final String PATH_ACTION_GENERATE = "generate"; + public static final String PATH_ACTION_PUBLISH = "publish"; + public static final String PATH_ACTION_REVIEW_REQUEST = "review-request"; + public static final String PATH_ACTION_REVIEW_APPROVE = "review-approve"; + public static final String PATH_ACTION_REVIEW_REJECT = "review-reject"; + public static final String PATH_ACTION_REVIEW_LIST = "review-list"; public static final String PATH_ACTION_UPDATE_RESOURCE_TYPES = "update-resource-types"; public static final String PATH_ACTION_UPDATE_SML_DATA = "update-sml-integration-data"; public static final String PATH_ACTION_RESET_CREDENTIAL_REQUEST = "request-reset-credential"; @@ -133,14 +139,24 @@ public class ResourceConstants { public static final String CONTEXT_PATH_EDIT_SUBRESOURCE = CONTEXT_PATH_EDIT_RESOURCE_SHORT + URL_PATH_SEPARATOR + PATH_RESOURCE_TYPE_SUBRESOURCE; public static final String SUB_CONTEXT_PATH_EDIT_SUBRESOURCE_DELETE = "{" + PATH_PARAM_ENC_SUBRESOURCE_ID + "}" + URL_PATH_SEPARATOR + PATH_ACTION_DELETE; - public static final String CONTEXT_PATH_EDIT_DOCUMENT = CONTEXT_PATH_EDIT + URL_PATH_SEPARATOR +PATH_RESOURCE_TYPE_RESOURCE +URL_PATH_SEPARATOR + "{" + PATH_PARAM_ENC_RESOURCE_ID + "}"; - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET = PATH_RESOURCE_TYPE_DOCUMENT; - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_VALIDATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET + URL_PATH_SEPARATOR + PATH_ACTION_VALIDATE; - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_GENERATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET + URL_PATH_SEPARATOR + PATH_ACTION_GENERATE; - - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET_SUBRESOURCE = PATH_RESOURCE_TYPE_SUBRESOURCE + URL_PATH_SEPARATOR + "{" + PATH_PARAM_ENC_SUBRESOURCE_ID + "}" + URL_PATH_SEPARATOR + PATH_RESOURCE_TYPE_DOCUMENT; - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_VALIDATE; - public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_GENERATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_GENERATE; + public static final String CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE = CONTEXT_PATH_EDIT + URL_PATH_SEPARATOR +PATH_RESOURCE_TYPE_RESOURCE +URL_PATH_SEPARATOR + "{" + PATH_PARAM_ENC_RESOURCE_ID + "}"; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE = PATH_RESOURCE_TYPE_DOCUMENT; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_VALIDATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_VALIDATE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_GENERATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_GENERATE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_PUBLISH = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_PUBLISH; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_REVIEW = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_REQUEST; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_APPROVE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_APPROVE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_REJECT = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_REJECT; + + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE = PATH_RESOURCE_TYPE_SUBRESOURCE + URL_PATH_SEPARATOR + "{" + PATH_PARAM_ENC_SUBRESOURCE_ID + "}" + URL_PATH_SEPARATOR + PATH_RESOURCE_TYPE_DOCUMENT; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_VALIDATE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_GENERATE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_GENERATE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_PUBLISH = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_PUBLISH; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_REVIEW = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_REQUEST; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_APPROVE = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_APPROVE; + public static final String SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_REJECT = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE + URL_PATH_SEPARATOR + PATH_ACTION_REVIEW_REJECT; + + public static final String CONTEXT_PATH_EDIT_REVIEW = CONTEXT_PATH_EDIT + URL_PATH_SEPARATOR + PATH_RESOURCE_TYPE_REVIEW; // public public static final String CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT = CONTEXT_PATH_PUBLIC + PATH_ACTION_SEARCH; public static final String CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT_METADATA = CONTEXT_PATH_PUBLIC_SEARCH_PARTICIPANT + "/metadata"; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditController.java index 11d326265f1606c8d61e471d9fa6043f35363465..5c7e29a88d92807a0c01cbc5d863e1e4ff0b44ec 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditController.java @@ -19,6 +19,8 @@ package eu.europa.ec.edelivery.smp.ui.edit; +import eu.europa.ec.edelivery.smp.auth.SMPAuthorizationService; +import eu.europa.ec.edelivery.smp.data.enums.DocumentVersionEventType; import eu.europa.ec.edelivery.smp.data.ui.DocumentRO; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; @@ -29,6 +31,8 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; +import java.util.Collections; + import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; /** @@ -40,20 +44,23 @@ import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; * @since 5.0 */ @RestController -@RequestMapping(value = ResourceConstants.CONTEXT_PATH_EDIT_DOCUMENT) +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE) public class DocumentEditController { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(DocumentEditController.class); private final UIDocumentService uiDocumentService; + private final SMPAuthorizationService smpAuthorizationService; - public DocumentEditController(UIDocumentService uiDocumentService) { + public DocumentEditController(UIDocumentService uiDocumentService, + SMPAuthorizationService smpAuthorizationService) { this.uiDocumentService = uiDocumentService; + this.smpAuthorizationService = smpAuthorizationService; } - - @GetMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + @GetMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + - "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") public DocumentRO getDocumentForResource(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @RequestParam(value = PARAM_NAME_VERSION, defaultValue = "-1") int version) { @@ -62,9 +69,10 @@ public class DocumentEditController { return uiDocumentService.getDocumentForResource(resourceId, version); } - @GetMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET_SUBRESOURCE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) + @GetMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + - "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") public DocumentRO getDocumentForSubResource(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, @@ -72,23 +80,32 @@ public class DocumentEditController { logAdminAccess("getDocumentForResource"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); - return uiDocumentService.getDocumentForSubResource(subresourceId, resourceId, version); + DocumentRO document = uiDocumentService.getDocumentForSubResource(subresourceId, resourceId, version); + if (!smpAuthorizationService.isResourceAdministrator(userEncId)) { + document.setDocumentVersions(Collections.emptyList()); + } + return document; } - @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_VALIDATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_VALIDATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + - "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") - public void validateDocument(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, - @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, - @RequestBody DocumentRO document) { + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") + public void validateResourceDocument(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody DocumentRO document) { logAdminAccess("validateDocumentForResource"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); uiDocumentService.validateDocumentForResource(resourceId, document); + if (!smpAuthorizationService.isResourceAdministrator(userEncId)) { + document.setDocumentVersions(Collections.emptyList()); + } } @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_VALIDATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + - "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") public void validateSubresourceDocument(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, @@ -100,15 +117,15 @@ public class DocumentEditController { } - @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GENERATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_GENERATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") - public DocumentRO generateDocument(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, - @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, - @RequestBody(required = false) DocumentRO document) { - logAdminAccess("generateDocument"); + public DocumentRO generateResourceDocument(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody(required = false) DocumentRO document) { + logAdminAccess("generateResourceDocument"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); - return uiDocumentService.generateDocumentForResource(resourceId, document); + return uiDocumentService.generateDocumentForResource(resourceId); } @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_GENERATE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) @@ -118,13 +135,132 @@ public class DocumentEditController { @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, @RequestBody(required = false) DocumentRO document) { - logAdminAccess("generateDocument"); + logAdminAccess("generateSubresourceDocument"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); - return uiDocumentService.generateDocumentForSubresource(subresourceId, resourceId, document); + return uiDocumentService.generateDocumentForSubresource(subresourceId, resourceId); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_PUBLISH, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + public DocumentRO publishResourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("publishResourceDocument"); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.publishDocumentVersionForResource(resourceId, documentId, document.getPayloadVersion()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_PUBLISH, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + public DocumentRO publishSubresourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("publishSubresourceDocument"); + Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.publishDocumentVersionForSubresource(subresourceId, resourceId, documentId, document.getPayloadVersion()); } - @PutMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET, + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_REVIEW, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + public DocumentRO requestReviewResourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("requestReviewDocument"); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.requestReviewDocumentVersionForResource(resourceId, documentId, document.getPayloadVersion()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_REVIEW, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + public DocumentRO requestReviewSubresourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("requestReviewDocument"); + Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.requestReviewDocumentVersionForSubresource(subresourceId, resourceId, documentId, document.getPayloadVersion()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_APPROVE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceReviewer(#resourceEncId)") + public DocumentRO approveResourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("approveResourceDocument"); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.reviewActionDocumentVersionForResource(resourceId, documentId, document.getPayloadVersion(), + DocumentVersionEventType.APPROVE, + document.getActionMessage()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_APPROVE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and @smpAuthorizationService.isResourceAdministrator(#resourceEncId)") + public DocumentRO approveSubresourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("approveSubresourceDocument"); + Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.reviewActionDocumentVersionForSubresource(subresourceId, resourceId, documentId, + document.getPayloadVersion(), + DocumentVersionEventType.APPROVE, + document.getActionMessage()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_REJECT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") + public DocumentRO rejectResourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("rejectResourceDocument"); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.reviewActionDocumentVersionForResource(resourceId, documentId, document.getPayloadVersion(), + DocumentVersionEventType.REJECT, + document.getActionMessage()); + } + + @PostMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE_REJECT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and (@smpAuthorizationService.isResourceAdministrator(#resourceEncId)" + + " or @smpAuthorizationService.isResourceReviewer(#resourceEncId))") + public DocumentRO rejectSubresourceDocumentVersion(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, + @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, + @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, + @RequestBody DocumentRO document) { + logAdminAccess("rejectSubresourceDocument"); + Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); + Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); + Long documentId = SessionSecurityUtils.decryptEntityId(document.getDocumentId()); + return uiDocumentService.reviewActionDocumentVersionForSubresource(subresourceId, + resourceId, + documentId, + document.getPayloadVersion(), + DocumentVersionEventType.REJECT, + document.getActionMessage()); + } + + + @PutMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + @@ -132,12 +268,16 @@ public class DocumentEditController { public DocumentRO saveDocumentForResource(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @RequestBody DocumentRO document) { - logAdminAccess("validateDocumentForResource"); + logAdminAccess("saveResourceDocument"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); - return uiDocumentService.saveDocumentForResource(resourceId, document); + Long referenceDocumentId = null; + if (document.getReferenceDocumentId() != null) { + referenceDocumentId = SessionSecurityUtils.decryptEntityId(document.getReferenceDocumentId()); + } + return uiDocumentService.saveDocumentForResource(resourceId, document, referenceDocumentId); } - @PutMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET_SUBRESOURCE, + @PutMapping(path = SUB_CONTEXT_PATH_EDIT_DOCUMENT_SUBRESOURCE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + @@ -146,13 +286,12 @@ public class DocumentEditController { @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @PathVariable(PATH_PARAM_ENC_SUBRESOURCE_ID) String subresourceEncId, @RequestBody DocumentRO document) { - logAdminAccess("validateDocumentForResource"); + logAdminAccess("saveSubresourceDocument"); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); Long subresourceId = SessionSecurityUtils.decryptEntityId(subresourceEncId); return uiDocumentService.saveSubresourceDocumentForResource(subresourceId, resourceId, document); } - protected void logAdminAccess(String action) { LOG.info(SMPLogger.SECURITY_MARKER, "Admin Domain action [{}] by user [{}], ", action, SessionSecurityUtils.getSessionUserDetails()); } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java index 4360a4d77e93cbf625bd4c96ae62e8f157276cd7..7a7aae44932fd7907b9c3089de969527d249ad00 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditController.java @@ -40,7 +40,7 @@ import java.util.List; import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; /** - * Purpose of the DomainResource is to provide search method to retrieve configured domains in SMP. + * Purpose of the GroupEditController is to provide search method to retrieve configured groups in SMP. * base path for the resource includes two variables user who is editing and domain for the group * /ui/edit/rest/[user-id]/domain/[domain-id]/group * diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java index 0ca1c4e82dfac32281cebb572ffff4a36dab8a31..d96388580a02c70faa48d05ed2e804cbc939b11f 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ResourceEditController.java @@ -39,9 +39,10 @@ import org.springframework.web.bind.annotation.*; import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; /** - * Purpose of the DomainResource is to provide search method to retrieve configured domains in SMP. + * Purpose of the ResourceEditController is to provide edut methods to retrieve + * update resources in the DomiSMP. * base path for the resource includes two variables user who is editing and domain for the group - * /ui/edit/rest/[user-id]/domain/[domain-id]/group/[group-id]/resource + * /ui/edit/rest/[user-id]/domain/[domain-id]/group/[group-id]/resource\[resource-id] * * @author Joze Rihtarsic * @since 5.0 @@ -63,8 +64,9 @@ public class ResourceEditController { * resource-viewer it returns all Resources for the group where user is Resources viewer; * all-roles it returns all groups for the domain for user * - * @param userEncId - * @param groupEncId + * @param userEncId logged user identifier + * @param domainEncId domain identifier + * @param groupEncId group identifier * @param forRole * @return */ @@ -97,6 +99,14 @@ public class ResourceEditController { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "ResourcesForGroups", "Unknown parameter type [" + forRole + "]!"); } + /** + * Methods enables to group admin to delete resource from the group + * @param userEncId logged user identifier + * @param domainEncId domain identifier + * @param groupEncId group identifier + * @param resourceEncId resource identifier + * @return the deleted ResourceRO + */ @DeleteMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and @smpAuthorizationService.isGroupAdministrator(#groupEncId)") public ResourceRO deleteResourceFromGroup(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @@ -110,6 +120,13 @@ public class ResourceEditController { return uiResourceService.deleteResourceFromGroup(resourceId, groupId, domainId); } + /** + * Methods enables to group admin to create resource on the group + * @param userEncId logged user identifier + * @param domainEncId domain identifier + * @param groupEncId group identifier + * @return the created ResourceRO data + */ @PutMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_CREATE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and @smpAuthorizationService.isGroupAdministrator(#groupEncId)") public ResourceRO createResource(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @@ -123,20 +140,42 @@ public class ResourceEditController { return uiResourceService.createResourceForGroup(resourceRO, groupId, domainId, userId); } + /** + * Method allows Group admin and Resource admin to change + * resource visibility and enable/disable review flow. + * @param userEncId logged user identifier + * @param domainEncId domain identifier + * @param groupEncId group identifier + * @param resourceEncId resource identifier + * @param resourceRO updated resource data + * @return the updated ResourceRO data + */ @PostMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_UPDATE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) - @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and @smpAuthorizationService.isGroupAdministrator(#groupEncId)") + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) " + + "and (@smpAuthorizationService.isGroupAdministrator(#groupEncId) or @smpAuthorizationService.isResourceAdministrator(#resourceEncId))") public ResourceRO updateResource(@PathVariable(PATH_PARAM_ENC_USER_ID) String userEncId, @PathVariable(PATH_PARAM_ENC_DOMAIN_ID) String domainEncId, @PathVariable(PATH_PARAM_ENC_GROUP_ID) String groupEncId, @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @RequestBody ResourceRO resourceRO) { - logAdminAccess("createResource"); + logAdminAccess("updateResource"); Long domainId = SessionSecurityUtils.decryptEntityId(domainEncId); Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); return uiResourceService.updateResourceForGroup(resourceRO, resourceId, groupId, domainId); } + /** + * Method returns list of members for the resource for group and resource + * @param userEncId + * @param domainEncId + * @param groupEncId + * @param resourceEncId + * @param page + * @param pageSize + * @param filter + * @return + */ @GetMapping(path = SUB_CONTEXT_PATH_EDIT_RESOURCE_MEMBER, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userEncId) and" + @@ -163,7 +202,7 @@ public class ResourceEditController { @PathVariable(PATH_PARAM_ENC_RESOURCE_ID) String resourceEncId, @RequestBody MemberRO memberRO) { - LOG.info("add member to group"); + LOG.debug("Add member to group"); Long groupId = SessionSecurityUtils.decryptEntityId(groupEncId); Long resourceId = SessionSecurityUtils.decryptEntityId(resourceEncId); Long memberId = memberRO.getMemberId() == null ? null : SessionSecurityUtils.decryptEntityId(memberRO.getMemberId()); @@ -193,7 +232,7 @@ public class ResourceEditController { } protected void logAdminAccess(String action) { - LOG.info(SMPLogger.SECURITY_MARKER, "Admin Domain action [{}] by user [{}], ", action, SessionSecurityUtils.getSessionUserDetails()); + LOG.info(SMPLogger.SECURITY_MARKER, "Group/Resource admin action [{}] by user [{}], ", action, SessionSecurityUtils.getSessionUserDetails()); } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ReviewEditController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ReviewEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..79b5b30e342b009b37587f0f2cfc91e69d168a5a --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/edit/ReviewEditController.java @@ -0,0 +1,81 @@ +/*- + * #START_LICENSE# + * smp-webapp + * %% + * Copyright (C) 2017 - 2024 European Commission | eDelivery | DomiSMP + * %% + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent + * versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and limitations under the Licence. + * #END_LICENSE# + */ +package eu.europa.ec.edelivery.smp.ui.edit; + + +import eu.europa.ec.edelivery.smp.data.ui.ReviewDocumentVersionRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.logging.SMPLogger; +import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.ui.UIDocumentService; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import eu.europa.ec.edelivery.smp.utils.SessionSecurityUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.*; + +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.*; + +/** + * Purpose of the DomainResource is to provide search method to retrieve configured domains in SMP. + * base path for the resource includes two variables user who is editing and domain for the group + * /ui/edit/rest/[user-id]/domain/[domain-id]/group/[group-id]/resource + * + * @author Joze Rihtarsic + * @since 5.0 + */ +@RestController +@RequestMapping(value = ResourceConstants.CONTEXT_PATH_EDIT_REVIEW) +public class ReviewEditController { + + private static final SMPLogger LOG = SMPLoggerFactory.getLogger(ReviewEditController.class); + private final UIDocumentService uiDocumentService; + + public ReviewEditController(UIDocumentService uiDocumentService) { + this.uiDocumentService = uiDocumentService; + } + + /** + * Method returns Users list of alerts. To access the list user must be logged in. + * <p> + * + * @param encUserId - encrypted user id (from session) - used for authorization check + * @param page - page number (0..n) + * @param pageSize - page size (0..n) - number of results on page/max number of returned results. + * @param orderBy - order by field + * @param orderType - order type (asc, desc) + * @return ServiceResult<AlertRO> - list of alerts + */ + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#encUserId)") + @GetMapping(path = "/", produces = {MimeTypeUtils.APPLICATION_JSON_VALUE}) + public ServiceResult<ReviewDocumentVersionRO> getUserReviewTasks( + @PathVariable("user-id") String encUserId, + @RequestParam(value = PARAM_PAGINATION_PAGE, defaultValue = "0") int page, + @RequestParam(value = PARAM_PAGINATION_PAGE_SIZE, defaultValue = "10") int pageSize, + @RequestParam(value = PARAM_PAGINATION_ORDER_BY, defaultValue = "id", required = false) String orderBy, + @RequestParam(value = PARAM_PAGINATION_ORDER_TYPE, defaultValue = "desc", required = false) String orderType, + @RequestParam(value = PARAM_PAGINATION_FILTER, required = false) Object filter + ) { + LOG.info("Search for page: {}, page size: {}", page, pageSize); + Long userId = SessionSecurityUtils.decryptEntityId(encUserId); + // set filter to current user + return uiDocumentService.getDocumentReviewListForUser(userId, page, pageSize, orderBy, orderType, filter); + } +} + diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserController.java index c0bb7f58f3451535fff8a724cc947c12d6884869..e46095ba427ebe7f57ae93e3168724349131e0df 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/UserController.java @@ -329,6 +329,7 @@ public class UserController { node.addChild(new NavigationTreeNodeRO("edit-domain", "navigation.label.edit.domains", "account_circle", "edit-domain")); node.addChild(new NavigationTreeNodeRO("edit-group", "navigation.label.edit.groups", "group", "edit-group")); node.addChild(new NavigationTreeNodeRO("edit-resource", "navigation.label.edit.resources", "article", "edit-resource")); + node.addChild(new NavigationTreeNodeRO("review-tasks", "navigation.label.review.tasks", "task", "review-tasks")); return node; } } diff --git a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl index f33ba8d00756e7f4b4216169ebb739ca0b41400c..dec99d490e27e3e13ce08c4a6e7bc36f699a4c6c 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb-drop.ddl @@ -35,6 +35,10 @@ drop foreign key FKqjh6vxvb5tg0tvbkvi3k3xhe6; + alter table SMP_DOCUMENT + drop + foreign key FKbytp2kp8g3pj8qfp1g6a2g7p; + alter table SMP_DOCUMENT_AUD drop foreign key FKh9epnme26i271eixtvrpqejvi; @@ -55,6 +59,10 @@ drop foreign key FK4glqiu73939kpyyb6bhw822k3; + alter table SMP_DOCUMENT_VERSION_EVENT + drop + foreign key FK6es2svpoxyrnt1h05c9junmdn; + alter table SMP_DOMAIN_AUD drop foreign key FK35qm8xmi74kfenugeonijodsg; @@ -211,6 +219,8 @@ drop table if exists SMP_DOCUMENT_VERSION_AUD; + drop table if exists SMP_DOCUMENT_VERSION_EVENT; + drop table if exists SMP_DOMAIN; drop table if exists SMP_DOMAIN_AUD; diff --git a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl index 2b82adba38fd9a546778ad24572f81c8c26605f4..ca073b2a7e19e543667c036b4c3f0f93372c540a 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl @@ -164,6 +164,8 @@ CURRENT_VERSION integer not null, MIME_TYPE varchar(128) CHARACTER SET utf8 COLLATE utf8_bin, NAME varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, + SHARING_ENABLED bit, + FK_REF_DOCUMENT_ID bigint, primary key (ID) ) comment='SMP document entity for resources and subresources' ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -176,6 +178,8 @@ CURRENT_VERSION integer, MIME_TYPE varchar(128) CHARACTER SET utf8 COLLATE utf8_bin, NAME varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, + SHARING_ENABLED bit, + FK_REF_DOCUMENT_ID bigint, primary key (ID, REV) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -210,6 +214,7 @@ CREATED_ON datetime not null, LAST_UPDATED_ON datetime not null, DOCUMENT_CONTENT longblob comment 'Document content', + STATUS varchar(255) CHARACTER SET utf8 COLLATE utf8_bin not null comment 'Document version status', VERSION integer not null, FK_DOCUMENT_ID bigint, primary key (ID) @@ -222,11 +227,25 @@ CREATED_ON datetime, LAST_UPDATED_ON datetime, DOCUMENT_CONTENT longblob, + STATUS varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, VERSION integer, FK_DOCUMENT_ID bigint, primary key (ID, REV) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + create table SMP_DOCUMENT_VERSION_EVENT ( + ID bigint not null auto_increment comment 'Unique document version event identifier', + CREATED_ON datetime not null, + LAST_UPDATED_ON datetime not null, + DETAILS varchar(1024) CHARACTER SET utf8 COLLATE utf8_bin comment 'Details of the event', + EVENT_ON datetime comment 'Date time of the event', + EVENT_SOURCE varchar(255) CHARACTER SET utf8 COLLATE utf8_bin not null comment 'Event source UI, API', + EVENT_TYPE varchar(255) CHARACTER SET utf8 COLLATE utf8_bin not null comment 'Document version event type', + EVENT_BY_USERNAME varchar(64) CHARACTER SET utf8 COLLATE utf8_bin comment 'username identifier of the user who triggered the event', + FK_DOCUMENT_VERSION_ID bigint, + primary key (ID) + ) comment='Document version Events.' ENGINE=InnoDB DEFAULT CHARSET=utf8; + create table SMP_DOMAIN ( ID bigint not null auto_increment comment 'Unique domain id', CREATED_ON datetime not null, @@ -411,7 +430,8 @@ LAST_UPDATED_ON datetime not null, IDENTIFIER_SCHEME varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, IDENTIFIER_VALUE varchar(256) CHARACTER SET utf8 COLLATE utf8_bin not null, - SML_REGISTERED bit not null, + REVIEW_ENABLED bit, + SML_REGISTERED bit, VISIBILITY varchar(128) CHARACTER SET utf8 COLLATE utf8_bin, FK_DOCUMENT_ID bigint not null, FK_DOREDEF_ID bigint not null, @@ -427,6 +447,7 @@ LAST_UPDATED_ON datetime, IDENTIFIER_SCHEME varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, IDENTIFIER_VALUE varchar(256) CHARACTER SET utf8 COLLATE utf8_bin, + REVIEW_ENABLED bit, SML_REGISTERED bit, VISIBILITY varchar(128) CHARACTER SET utf8 COLLATE utf8_bin, FK_DOCUMENT_ID bigint, @@ -469,6 +490,7 @@ ID bigint not null auto_increment, CREATED_ON datetime not null, LAST_UPDATED_ON datetime not null, + PERMISSION_REVIEW bit comment 'User permission to review the resource document', MEMBERSHIP_ROLE varchar(64) CHARACTER SET utf8 COLLATE utf8_bin, FK_RESOURCE_ID bigint, FK_USER_ID bigint, @@ -481,6 +503,7 @@ REVTYPE tinyint, CREATED_ON datetime, LAST_UPDATED_ON datetime, + PERMISSION_REVIEW bit, MEMBERSHIP_ROLE varchar(64) CHARACTER SET utf8 COLLATE utf8_bin, FK_RESOURCE_ID bigint, FK_USER_ID bigint, @@ -596,6 +619,7 @@ create index SMP_DOCVER_DOCUMENT_IDX on SMP_DOCUMENT_VERSION (FK_DOCUMENT_ID); alter table SMP_DOCUMENT_VERSION add constraint SMP_DOCVER_UNIQ_VERSION_IDX unique (FK_DOCUMENT_ID, VERSION); +create index SMP_DOCVEREVNT_DOCVER_IDX on SMP_DOCUMENT_VERSION_EVENT (FK_DOCUMENT_VERSION_ID); alter table SMP_DOMAIN add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE); @@ -692,6 +716,11 @@ create index SMP_SMD_DOC_SCH_IDX on SMP_SUBRESOURCE (IDENTIFIER_SCHEME); foreign key (REV) references SMP_REV_INFO (id); + alter table SMP_DOCUMENT + add constraint FKbytp2kp8g3pj8qfp1g6a2g7p + foreign key (FK_REF_DOCUMENT_ID) + references SMP_DOCUMENT (ID); + alter table SMP_DOCUMENT_AUD add constraint FKh9epnme26i271eixtvrpqejvi foreign key (REV) @@ -717,6 +746,11 @@ create index SMP_SMD_DOC_SCH_IDX on SMP_SUBRESOURCE (IDENTIFIER_SCHEME); foreign key (REV) references SMP_REV_INFO (id); + alter table SMP_DOCUMENT_VERSION_EVENT + add constraint FK6es2svpoxyrnt1h05c9junmdn + foreign key (FK_DOCUMENT_VERSION_ID) + references SMP_DOCUMENT_VERSION (ID); + alter table SMP_DOMAIN_AUD add constraint FK35qm8xmi74kfenugeonijodsg foreign key (REV) diff --git a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl index 45b4fba095365730d7182e3040ceea70e09f062f..58c36e490cea75b770f94b17f440d39a49d8eaf6 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g-drop.ddl @@ -35,6 +35,8 @@ drop table SMP_DOCUMENT_VERSION_AUD cascade constraints; + drop table SMP_DOCUMENT_VERSION_EVENT cascade constraints; + drop table SMP_DOMAIN cascade constraints; drop table SMP_DOMAIN_AUD cascade constraints; @@ -101,6 +103,8 @@ drop sequence SMP_DOCUMENT_VERSION_SEQ; + drop sequence SMP_DOCVER_EVENT_SEQ; + drop sequence SMP_DOMAIN_CONF_SEQ; drop sequence SMP_DOMAIN_MEMBER_SEQ; diff --git a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl index 953bb1be081432cc584862d50ad970fbebfd56f4..f7156c6dfdcc4c29dd03ee72ced09f7e3f62506f 100644 --- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl +++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl @@ -8,6 +8,7 @@ create sequence SMP_CREDENTIAL_SEQ start with 1 increment by 1; create sequence SMP_DOC_PROP_SEQ start with 1 increment by 1; create sequence SMP_DOCUMENT_SEQ start with 1 increment by 1; create sequence SMP_DOCUMENT_VERSION_SEQ start with 1 increment by 1; +create sequence SMP_DOCVER_EVENT_SEQ start with 1 increment by 1; create sequence SMP_DOMAIN_CONF_SEQ start with 1 increment by 1; create sequence SMP_DOMAIN_MEMBER_SEQ start with 1 increment by 1; create sequence SMP_DOMAIN_RESOURCE_DEF_SEQ start with 1 increment by 1; @@ -283,6 +284,8 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; CURRENT_VERSION number(10,0) not null, MIME_TYPE varchar2(128 char), NAME varchar2(255 char), + SHARING_ENABLED number(1,0), + FK_REF_DOCUMENT_ID number(19,0), primary key (ID) ); @@ -301,6 +304,8 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; CURRENT_VERSION number(10,0), MIME_TYPE varchar2(128 char), NAME varchar2(255 char), + SHARING_ENABLED number(1,0), + FK_REF_DOCUMENT_ID number(19,0), primary key (ID, REV) ); @@ -341,6 +346,7 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; CREATED_ON timestamp not null, LAST_UPDATED_ON timestamp not null, DOCUMENT_CONTENT blob, + STATUS varchar2(255 char) not null, VERSION number(10,0) not null, FK_DOCUMENT_ID number(19,0), primary key (ID) @@ -355,6 +361,9 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; comment on column SMP_DOCUMENT_VERSION.DOCUMENT_CONTENT is 'Document content'; + comment on column SMP_DOCUMENT_VERSION.STATUS is + 'Document version status'; + create table SMP_DOCUMENT_VERSION_AUD ( ID number(19,0) not null, REV number(19,0) not null, @@ -362,11 +371,46 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; CREATED_ON timestamp, LAST_UPDATED_ON timestamp, DOCUMENT_CONTENT blob, + STATUS varchar2(255 char), VERSION number(10,0), FK_DOCUMENT_ID number(19,0), primary key (ID, REV) ); + create table SMP_DOCUMENT_VERSION_EVENT ( + ID number(19,0) not null, + CREATED_ON timestamp not null, + LAST_UPDATED_ON timestamp not null, + DETAILS varchar2(1024 char), + EVENT_ON timestamp, + EVENT_SOURCE varchar2(255 char) not null, + EVENT_TYPE varchar2(255 char) not null, + EVENT_BY_USERNAME varchar2(64 char), + FK_DOCUMENT_VERSION_ID number(19,0), + primary key (ID) + ); + + comment on table SMP_DOCUMENT_VERSION_EVENT is + 'Document version Events.'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.ID is + 'Unique document version event identifier'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.DETAILS is + 'Details of the event'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.EVENT_ON is + 'Date time of the event'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.EVENT_SOURCE is + 'Event source UI, API'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.EVENT_TYPE is + 'Document version event type'; + + comment on column SMP_DOCUMENT_VERSION_EVENT.EVENT_BY_USERNAME is + 'username identifier of the user who triggered the event'; + create table SMP_DOMAIN ( ID number(19,0) not null, CREATED_ON timestamp not null, @@ -626,7 +670,8 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; LAST_UPDATED_ON timestamp not null, IDENTIFIER_SCHEME varchar2(256 char), IDENTIFIER_VALUE varchar2(256 char) not null, - SML_REGISTERED number(1,0) not null, + REVIEW_ENABLED number(1,0), + SML_REGISTERED number(1,0), VISIBILITY varchar2(128 char), FK_DOCUMENT_ID number(19,0) not null, FK_DOREDEF_ID number(19,0) not null, @@ -648,6 +693,7 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; LAST_UPDATED_ON timestamp, IDENTIFIER_SCHEME varchar2(256 char), IDENTIFIER_VALUE varchar2(256 char), + REVIEW_ENABLED number(1,0), SML_REGISTERED number(1,0), VISIBILITY varchar2(128 char), FK_DOCUMENT_ID number(19,0), @@ -699,18 +745,23 @@ create sequence SMP_USER_SEQ start with 1 increment by 1; ID number(19,0) not null, CREATED_ON timestamp not null, LAST_UPDATED_ON timestamp not null, + PERMISSION_REVIEW number(1,0), MEMBERSHIP_ROLE varchar2(64 char), FK_RESOURCE_ID number(19,0), FK_USER_ID number(19,0), primary key (ID) ); + comment on column SMP_RESOURCE_MEMBER.PERMISSION_REVIEW is + 'User permission to review the resource document'; + create table SMP_RESOURCE_MEMBER_AUD ( ID number(19,0) not null, REV number(19,0) not null, REVTYPE number(3,0), CREATED_ON timestamp, LAST_UPDATED_ON timestamp, + PERMISSION_REVIEW number(1,0), MEMBERSHIP_ROLE varchar2(64 char), FK_RESOURCE_ID number(19,0), FK_USER_ID number(19,0), @@ -868,6 +919,7 @@ create index SMP_DOCVER_DOCUMENT_IDX on SMP_DOCUMENT_VERSION (FK_DOCUMENT_ID); alter table SMP_DOCUMENT_VERSION add constraint SMP_DOCVER_UNIQ_VERSION_IDX unique (FK_DOCUMENT_ID, VERSION); +create index SMP_DOCVEREVNT_DOCVER_IDX on SMP_DOCUMENT_VERSION_EVENT (FK_DOCUMENT_VERSION_ID); alter table SMP_DOMAIN add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE); @@ -964,6 +1016,11 @@ create index SMP_SMD_DOC_SCH_IDX on SMP_SUBRESOURCE (IDENTIFIER_SCHEME); foreign key (REV) references SMP_REV_INFO; + alter table SMP_DOCUMENT + add constraint FKbytp2kp8g3pj8qfp1g6a2g7p + foreign key (FK_REF_DOCUMENT_ID) + references SMP_DOCUMENT; + alter table SMP_DOCUMENT_AUD add constraint FKh9epnme26i271eixtvrpqejvi foreign key (REV) @@ -989,6 +1046,11 @@ create index SMP_SMD_DOC_SCH_IDX on SMP_SUBRESOURCE (IDENTIFIER_SCHEME); foreign key (REV) references SMP_REV_INFO; + alter table SMP_DOCUMENT_VERSION_EVENT + add constraint FK6es2svpoxyrnt1h05c9junmdn + foreign key (FK_DOCUMENT_VERSION_ID) + references SMP_DOCUMENT_VERSION; + alter table SMP_DOMAIN_AUD add constraint FK35qm8xmi74kfenugeonijodsg foreign key (REV) diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditControllerIT.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditControllerIT.java index 0b610f028ee3ef4937786164e1c18b52366c493a..85ce7be69abfd552daafd019a00cdfa39157c44e 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditControllerIT.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/DocumentEditControllerIT.java @@ -45,13 +45,13 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class DocumentEditControllerIT extends AbstractControllerTest { - private static final String PATH = CONTEXT_PATH_EDIT_DOCUMENT; + private static final String PATH = CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE; @Autowired protected UIDocumentService documentService; @BeforeEach - public void setup() throws IOException { + public void setupData() throws IOException { super.setup(); } @@ -70,7 +70,7 @@ class DocumentEditControllerIT extends AbstractControllerTest { ResourceRO resourceRO = addResourceToGroup(session, domainRO, groupRO, userRO); // when - MvcResult result = mvc.perform(get(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET, + MvcResult result = mvc.perform(get(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE, userRO.getUserId(), resourceRO.getResourceId()) .session(session) .with(csrf()) @@ -79,7 +79,10 @@ class DocumentEditControllerIT extends AbstractControllerTest { // then DocumentRO documentRo = getObjectFromResponse(result, DocumentRO.class); assertNotNull(documentRo); - assertTrue(documentRo.getAllVersions().isEmpty()); // was just created without document + assertEquals(1, documentRo.getAllVersions().size()); + assertEquals(1, documentRo.getAllVersions().get(0)); + assertEquals(1, documentRo.getCurrentResourceVersion()); + assertNotNull(documentRo.getPayload()); } @Test @@ -99,7 +102,7 @@ class DocumentEditControllerIT extends AbstractControllerTest { ResourceRO resourceRO = resources.get(0); // when - MvcResult result = mvc.perform(get(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_GET, + MvcResult result = mvc.perform(get(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE, userRO.getUserId(), resourceRO.getResourceId()) .session(session) .with(csrf()) @@ -133,7 +136,7 @@ class DocumentEditControllerIT extends AbstractControllerTest { documentRo.setPayload(TestROUtils.createSMP10ServiceGroupPayload(resourceRO.getIdentifierValue(), resourceRO.getIdentifierScheme())); // when - mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_VALIDATE, + mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_VALIDATE, userRO.getUserId(), resourceRO.getResourceId()) .contentType(MediaType.APPLICATION_JSON) .content(getObjectMapper().writeValueAsBytes(documentRo)) @@ -164,7 +167,7 @@ class DocumentEditControllerIT extends AbstractControllerTest { documentRo.setPayload("invalid payload"); // when - MvcResult result = mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_VALIDATE, + MvcResult result = mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_VALIDATE, userRO.getUserId(), resourceRO.getResourceId()) .contentType(MediaType.APPLICATION_JSON) .content(getObjectMapper().writeValueAsBytes(documentRo)) @@ -196,7 +199,7 @@ class DocumentEditControllerIT extends AbstractControllerTest { ResourceRO resourceRO = resources.get(0); // when - MvcResult response = mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_GENERATE, + MvcResult response = mvc.perform(post(PATH + '/' + SUB_CONTEXT_PATH_EDIT_DOCUMENT_RESOURCE_GENERATE, userRO.getUserId(), resourceRO.getResourceId()) .session(session) .with(csrf()) diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceIT.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceIT.java index f4b21cc73155d135d5c27449de97497cdbf9d3e5..f1bf1e9b6796b7a44d27e915ceb7c718913f3bf4 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceIT.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/ApplicationResourceIT.java @@ -128,7 +128,7 @@ class ApplicationResourceIT { // when mvc.perform(get(PATH + "/config") .with(csrf())) - .andExpect(status().isUnauthorized()) + .andExpect(status().isForbidden()) .andReturn() .getResponse(); } diff --git a/smp-webapp/src/test/resources/cleanup-database.sql b/smp-webapp/src/test/resources/cleanup-database.sql index 847d25bd63fd55ac7d4b466f84a4861211b80bb2..ea5f8adc8321046e86f48b9fcb0f9c569ae457bc 100755 --- a/smp-webapp/src/test/resources/cleanup-database.sql +++ b/smp-webapp/src/test/resources/cleanup-database.sql @@ -18,6 +18,7 @@ DELETE FROM SMP_SUBRESOURCE; DELETE FROM SMP_SUBRESOURCE_AUD; DELETE FROM SMP_RESOURCE; DELETE FROM SMP_RESOURCE_AUD; +DELETE FROM SMP_DOCUMENT_VERSION_EVENT; DELETE FROM SMP_DOCUMENT_PROPERTY; DELETE FROM SMP_DOCUMENT_PROPERTY_AUD; DELETE FROM SMP_DOCUMENT_VERSION; diff --git a/smp-webapp/src/test/resources/webapp_integration_test_data.sql b/smp-webapp/src/test/resources/webapp_integration_test_data.sql index 9c726ef123a286caa3ee6e4df93f0a46c5b65e66..a15772c905726a694b58f0835b5255ee00c09b55 100644 --- a/smp-webapp/src/test/resources/webapp_integration_test_data.sql +++ b/smp-webapp/src/test/resources/webapp_integration_test_data.sql @@ -111,10 +111,10 @@ insert into SMP_DOCUMENT (ID, CURRENT_VERSION, MIME_TYPE, NAME,CREATED_ON, LAST_ (-2, 1, 'application/xml', 'service-group', NOW(), NOW()), (-3, 1, 'application/xml', 'service-metadata', NOW(), NOW()); -insert into SMP_DOCUMENT_VERSION (ID, FK_DOCUMENT_ID, VERSION, DOCUMENT_CONTENT, CREATED_ON, LAST_UPDATED_ON) values -(-1, -1, 1, '<ServiceGroup xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05"><ParticipantIdentifier scheme="iso6523-actorid-upis">0088:777002abzz777</ParticipantIdentifier><ServiceMetadataReferenceCollection/></ServiceGroup>' , NOW(), NOW()), -(-2, -2, 1, '<ServiceGroup xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05"><ParticipantIdentifier scheme="iso6523-actorid-upis">0088:777002abzz777</ParticipantIdentifier><ServiceMetadataReferenceCollection/></ServiceGroup>' , NOW(), NOW()), -(-3, -3, 1, FILE_READ('classpath:/input/ServiceMetadata.xml') , NOW(), NOW()); +insert into SMP_DOCUMENT_VERSION (ID, FK_DOCUMENT_ID, VERSION, STATUS, DOCUMENT_CONTENT, CREATED_ON, LAST_UPDATED_ON) values +(-1, -1, 1, 'PUBLISHED', '<ServiceGroup xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05"><ParticipantIdentifier scheme="iso6523-actorid-upis">0088:777002abzz777</ParticipantIdentifier><ServiceMetadataReferenceCollection/></ServiceGroup>' , NOW(), NOW()), +(-2, -2, 1, 'PUBLISHED', '<ServiceGroup xmlns="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05"><ParticipantIdentifier scheme="iso6523-actorid-upis">0088:777002abzz777</ParticipantIdentifier><ServiceMetadataReferenceCollection/></ServiceGroup>' , NOW(), NOW()), +(-3, -3, 1, 'PUBLISHED', FILE_READ('classpath:/input/ServiceMetadata.xml') , NOW(), NOW()); insert into SMP_RESOURCE ( ID, FK_GROUP_ID, FK_DOCUMENT_ID, FK_DOREDEF_ID, IDENTIFIER_SCHEME, IDENTIFIER_VALUE, SML_REGISTERED, VISIBILITY, CREATED_ON, LAST_UPDATED_ON) values (-1, 1, -1, 1, 'ehealth-actorid-qns', 'urn:australia:ncpb', 0, 'PUBLIC', NOW(), NOW()),