Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions projects/sonar/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
];
const fileConfig = {
enabled: true,
orderList: (a: any, b: any) => {

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type

Check warning on line 99 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type
return a.metadata.order - b.metadata.order;
},
infoExcludedFields: [
Expand All @@ -107,7 +107,7 @@
'permissions',
],
canAdd: () => of({ can: true, message: '' }),
canRead: (record: any, file: any) => {

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type

Check warning on line 110 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type
if (file && file?.metadata?.permissions?.read) {
return of({
can: file.metadata.permissions.read,
Expand All @@ -116,7 +116,7 @@
}
return of({ can: false, message: '' });
},
canUpdateMetadata: (record: any, file: any) => {

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Unexpected any. Specify a different type

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

Unexpected any. Specify a different type

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type

Check warning on line 119 in projects/sonar/src/app/app-routing.module.ts

View workflow job for this annotation

GitHub Actions / build (18.19.x)

Unexpected any. Specify a different type
if (file && file?.metadata?.permissions?.update) {
return of({
can: file.metadata.permissions.update,
Expand Down Expand Up @@ -156,7 +156,7 @@
export class AppRoutingModule {

private translateService: TranslateService = inject(TranslateService);
private router: Router= inject(Router);
private router: Router = inject(Router);
private route: ActivatedRoute = inject(ActivatedRoute);
private userService: UserService = inject(UserService);
private httpClient: HttpClient = inject(HttpClient);
Expand Down Expand Up @@ -279,7 +279,8 @@
Accept: 'application/rero+json'
}
},
files: {...fileConfig,
files: {
...fileConfig,
filterList: (item: any) => {
return (
item.metadata &&
Expand Down Expand Up @@ -396,6 +397,29 @@
editorSettings: {
longMode: true,
},
preCreateRecord: (record: any) => {
const user = this.userService.currentUser();
// add organisation reference to the new record
const organisationCode = user.organisation.code;
if (organisationCode) {
record.metadata.organisation = {
$ref: this.apiService.getRefEndpoint(
'organisations',
organisationCode
)
};
}
const userPid = user.pid;
if (userPid) {
record.metadata.user = {
Comment on lines +400 to +414
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard null currentUser() / organisation before dereference.

Line 403 assumes user and user.organisation are always present; if currentUser() isn’t ready, this will throw. Consider optional chaining (and still initialize record.metadata as per the earlier comment).

Proposed fix
-          const user = this.userService.currentUser();
+          const user = this.userService.currentUser();
           // add organisation reference to the new record
-          const organisationCode = user.organisation.code;
+          const organisationCode = user?.organisation?.code;
           if (organisationCode) {
             record.metadata.organisation = {
               $ref: this.apiService.getRefEndpoint(
                 'organisations',
                 organisationCode
               )
             };
           }
-          const userPid = user.pid;
+          const userPid = user?.pid;
           if (userPid) {
             record.metadata.user = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
preCreateRecord: (record: any) => {
const user = this.userService.currentUser();
// add organisation reference to the new record
const organisationCode = user.organisation.code;
if (organisationCode) {
record.metadata.organisation = {
$ref: this.apiService.getRefEndpoint(
'organisations',
organisationCode
)
};
}
const userPid = user.pid;
if (userPid) {
record.metadata.user = {
preCreateRecord: (record: any) => {
const user = this.userService.currentUser();
// add organisation reference to the new record
const organisationCode = user?.organisation?.code;
if (organisationCode) {
record.metadata.organisation = {
$ref: this.apiService.getRefEndpoint(
'organisations',
organisationCode
)
};
}
const userPid = user?.pid;
if (userPid) {
record.metadata.user = {
🤖 Prompt for AI Agents
In `@projects/sonar/src/app/app-routing.module.ts` around lines 400 - 414,
preCreateRecord currently dereferences this.userService.currentUser() and
user.organisation without checks; update the handler to guard against
null/undefined by ensuring record.metadata is initialized (e.g. if
(!record.metadata) record.metadata = {}), retrieve const user =
this.userService.currentUser() and only access user.organisation?.code and
user.pid if user exists (use optional chaining or an explicit if (user) check),
and only call this.apiService.getRefEndpoint('organisations', organisationCode)
when organisationCode is truthy; apply these checks around the existing
assignments in preCreateRecord to avoid runtime crashes.

$ref: this.apiService.getRefEndpoint(
'users',
userPid
)
};
}
return record;
},
Comment on lines +400 to +422
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ensure record.metadata exists before assignment.

The code assumes record.metadata already exists. If preCreateRecord is called with a record where metadata is undefined, this will throw a runtime error on line 404.

🛡️ Proposed defensive fix
         preCreateRecord: (record: any) => {
           // add organisation reference to the new record
           const organisationCode = this.userService.getOrganisationCode();
           if (organisationCode) {
+            record.metadata = record.metadata || {};
             record.metadata.organisation = {
               $ref: this.apiService.getRefEndpoint(
                 'organisations',
                 organisationCode
               )
             };
           }
           return record;
         },
🤖 Prompt for AI Agents
In `@projects/sonar/src/app/app-routing.module.ts` around lines 400 - 412,
preCreateRecord currently assumes record.metadata exists and will throw if it's
undefined; initialize record.metadata (e.g., if (!record.metadata)
record.metadata = {}) before assigning record.metadata.organisation, then
proceed to set the $ref using this.apiService.getRefEndpoint('organisations',
this.userService.getOrganisationCode()) only when organisationCode is truthy,
and finally return record; update the preCreateRecord implementation to
defensively create metadata to avoid runtime errors.

exportFormats: [
{
label: 'CSV',
Expand Down Expand Up @@ -474,8 +498,8 @@
if (user) {
/** Removes collections and subdivisions routes on the organisation shared */
if (!('isDedicated' in user.organisation) || !(user.organisation.isDedicated)) {
recordsRoutesConfiguration = recordsRoutesConfiguration
.filter(route => !(['collections', 'subdivisions'].includes(route.type)));
recordsRoutesConfiguration = recordsRoutesConfiguration
.filter(route => !(['collections', 'subdivisions'].includes(route.type)));
}

recordsRoutesConfiguration.forEach((config: any) => {
Expand Down Expand Up @@ -521,6 +545,7 @@
recordResource: config.recordResource || null,
exportFormats: config.exportFormats || null,
sortOptions: config.sortOptions || null,
preCreateRecord: config.preCreateRecord || null,
canAdd: () => this._can(config.type, 'add'),
canUpdate: (record: any) => this._can(config.type, 'update', record),
canDelete: (record: any) => this._can(config.type, 'delete', record),
Expand Down Expand Up @@ -634,7 +659,7 @@
private _documentAggregationsOrder(): Observable<any> {
return of(null).pipe(
switchMap(() => {
const {view} = this.route.snapshot.children[0].params;
const { view } = this.route.snapshot.children[0].params;

let params = new HttpParams();
if (view) {
Expand Down
4 changes: 3 additions & 1 deletion projects/sonar/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { BriefViewComponent as ProjectBriefViewComponent } from './record/projec
import { DetailComponent as ProjectDetailComponent } from './record/project/detail/detail.component';
import { BriefViewComponent as SubdivisionBriefViewComponent } from './record/subdivision/brief-view/brief-view.component';
import { UserComponent } from './record/user/user.component';
import { ValidationComponent } from './record/validation/validation.component';
import { UserService } from './user.service';
import { LicensePipe } from './record/document/license.pipe';
import { BucketNameService } from './bucket-name.service';
Expand Down Expand Up @@ -145,7 +146,8 @@ export function minElementError(err: any, field: FormlyFieldConfig) {
MetadataComponent,
FilesComponent,
SwisscoveryComponent,
LicensePipe
LicensePipe,
ValidationComponent
],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,8 @@ <h4 class="ui:flex-grow">
@if(!record.metadata.logs || record.metadata.logs.length === 1) {
<p-tag severity="contrast" [value]="'New' | translate" />
}
@switch(record.metadata.status) {
@case('in_progress') {
<p-tag severity="primary" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
@case('to_validate') {
<p-tag severity="info" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
@case('ask_for_changes') {
<p-tag severity="warn" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
@case('validated') {
<p-tag severity="success" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
@case('rejected') {
<p-tag severity="danger" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
@if(statusSeverity) {
<p-tag [severity]="statusSeverity" [value]="('deposit_status_' + record.metadata.status) | translate" />
}
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
import { Component, computed, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { Subscription } from 'rxjs';
import { VALIDATION_STATUS_SEVERITY } from '../../enum/validation';
import { UserService } from '../../user.service';

@Component({
Expand Down Expand Up @@ -48,6 +49,12 @@ export class BriefViewComponent implements OnInit, OnDestroy {
this.userService.hasRole(['moderator', 'admin', 'superuser'])
);

get statusSeverity(): string | null {
return this.record?.metadata?.status
? VALIDATION_STATUS_SEVERITY[this.record.metadata.status]
: null;
}

private subscription: Subscription = new Subscription();

ngOnInit(): void {
Expand Down
8 changes: 8 additions & 0 deletions projects/sonar/src/app/enum/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ export enum validation_action {
'REJECT' = 'reject',
'ASK_FOR_CHANGES' = 'ask_for_changes'
}

export const VALIDATION_STATUS_SEVERITY: Record<string, string> = {
[validation_status.IN_PROGRESS]: 'primary',
[validation_status.TO_VALIDATE]: 'info',
[validation_status.ASK_FOR_CHANGES]: 'warn',
[validation_status.VALIDATED]: 'success',
[validation_status.REJECTED]: 'danger'
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ <h5>
</h5>
<div class="ui:flex ui:gap-1">
<p-tag [value]="record.metadata.organisation.name" />
@if(record.metadata?.validation?.status !== validationStatus.VALIDATED) {
<p-tag severity="warn" [value]="'This record is not published' | translate" />
@if(validationSeverity) {
<p-tag [severity]="validationSeverity" [value]="record.metadata.validation.status | translate" />
}
</div>
<span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@
*/
import { Component } from '@angular/core';
import { ResultItem } from '@rero/ng-core';
import { validation_status } from '../../../enum/validation';
import { VALIDATION_STATUS_SEVERITY } from '../../../enum/validation';

@Component({
templateUrl: './brief-view.component.html',
standalone: false
})
export class BriefViewComponent implements ResultItem {
// Constant for validation status.
readonly validationStatus = validation_status;

// Record data.
record: any;

Expand All @@ -34,4 +31,10 @@ export class BriefViewComponent implements ResultItem {

// Detail URL object.
detailUrl: { link: string, external: boolean };

get validationSeverity(): string | null {
return this.record?.metadata?.validation?.status
? VALIDATION_STATUS_SEVERITY[this.record.metadata.validation.status]
: null;
}
}
106 changes: 106 additions & 0 deletions projects/sonar/src/app/record/validation/validation.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!--
SONAR User Interface
Copyright (C) 2021 RERO

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
@if(validation && user && (isModerator() || isOwner())) {
<p-panel [header]="'Validation' | translate">
<div class="ui:flex ui:flex-col ui:gap-2 ui:justify-center ui:p-2">
@if(validation.status === validationStatus.IN_PROGRESS || validation.status === validationStatus.ASK_FOR_CHANGES) {
<p-message class="ui:w-full" severity="warn" showTransitionOptions="0ms">
{{ "The record is currently in status \"\{\{ status \}\}\". It is not visible in public views." | translate: { status: (validation.status) | translate } }}
@if(isOwner()) {
<a href="#" class="alert-link" (click)="$event.preventDefault(); updateValidation(validationAction.PUBLISH)" translate>
Submit it
</a>.
}
</p-message>
}
@if(validation.status === validationStatus.TO_VALIDATE) {
<p-message class="ui:w-full" severity="warn" showTransitionOptions="0ms">
{{"The record is in validation. It will be reviewed by a moderator before publishing." | translate}}
</p-message>
}
@if(validation.status === validationStatus.REJECTED) {
<p-message class="ui:w-full" severity="error" showTransitionOptions="0ms">
{{ "The record has been rejected after a review from a moderator." | translate}}
</p-message>
}
@if(validation.status === validationStatus.VALIDATED) {
<p-message class="ui:w-full" severity="success" showTransitionOptions="0ms">
{{ "The record is currently published. It is visible in public views." | translate }}
</p-message>

}
@if(validation.status === validationStatus.TO_VALIDATE && isModerator()) {
<textarea
#comment
class="ui:min-w-lg ui:my-4"
pTextarea
[autoResize]="true"
[placeholder]="'Leave a comment...' | translate"
></textarea>
<div class="ui:flex ui:justify-center">
<p-buttongroup class="ui:mx-auto">
<p-button
size="large"
severity="success"
(onClick)="updateValidation(validationAction.APPROVE)"
[label]="'Approve' | translate"
/>
<p-button
size="large"
severity="warn"
(onClick)="updateValidation(validationAction.ASK_FOR_CHANGES)"
[label]="'Ask for changes' | translate"
/>
<p-button
size="large"
severity="danger"
(onClick)="updateValidation(validationAction.REJECT)"
[label]="'Reject' | translate"
/>
</p-buttongroup>
</div>
}
@if(validation.logs) {
<p-button
[text]="true"
[label]="(showLogs ? 'Hide logs' : 'Show logs') | translate"
(onClick)="showLogs = !showLogs"
/>
@if(showLogs) {
<p-table [value]="validation.logs" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header>
<tr>
<th translate>Status</th>
<th translate>Date</th>
<th translate>User</th>
<th translate>Comment</th>
</tr>
</ng-template>
<ng-template #body let-log>
<tr>
<td>{{ (log.status) | translate }}</td>
<td>{{ log.date | dateTranslate: 'medium' }}</td>
<td>{{ log.user.name }}</td>
<td [innerHtml]="log.comment | nl2br"></td>
</tr>
</ng-template>
</p-table>
}
}
</div>
</p-panel>
}
Loading
Loading