-
+
{{ 'ldn-new-service.form.label.url' | translate }}
-
- {{ 'ldn-new-service.form.error.url' | translate }}
-
+ [placeholder]="'ldn-new-service.form.placeholder.url' | translate" class="form-control"
+ formControlName="url"
+ id="url"
+ name="url"
+ type="text">
+ @if (formModel.get('url').invalid && formModel.get('url').touched) {
+
+ {{ 'ldn-new-service.form.error.url' | translate }}
+
+ }
{{ 'ldn-new-service.form.label.score' | translate }}
-
- {{ 'ldn-new-service.form.error.score' | translate }}
-
+ [placeholder]="'ldn-new-service.form.placeholder.score' | translate" formControlName="score"
+ id="score"
+ name="score"
+ min="0"
+ max="1"
+ step=".01"
+ class="form-control"
+ type="number">
+ @if (formModel.get('score').invalid && formModel.get('score').touched) {
+
+ {{ 'ldn-new-service.form.error.score' | translate }}
+
+ }
@@ -73,21 +81,23 @@
{{ 'ldn-new-service.form.label.ip-range' | translate }}
+ [placeholder]="'ldn-new-service.form.placeholder.lowerIp' | translate" class="form-control me-2"
+ formControlName="lowerIp"
+ id="lowerIp"
+ name="lowerIp"
+ type="text">
-
-
- {{ 'ldn-new-service.form.error.ipRange' | translate }}
+ [placeholder]="'ldn-new-service.form.placeholder.upperIp' | translate" class="form-control"
+ formControlName="upperIp"
+ id="upperIp"
+ name="upperIp"
+ type="text">
+ @if ((formModel.get('lowerIp').invalid && formModel.get('lowerIp').touched) || (formModel.get('upperIp').invalid && formModel.get('upperIp').touched)) {
+
+ {{ 'ldn-new-service.form.error.ipRange' | translate }}
+
+ }
{{ 'ldn-new-service.form.hint.ipRange' | translate }}
@@ -97,223 +107,258 @@
{{ 'ldn-new-service.form.label.ldnUrl' | translate }}
-
-
- {{ 'ldn-new-service.form.error.ldnurl' | translate }}
+ [placeholder]="'ldn-new-service.form.placeholder.ldnUrl' | translate" class="form-control"
+ formControlName="ldnUrl"
+ id="ldnUrl"
+ name="ldnUrl"
+ type="text">
+ @if (formModel.get('ldnUrl').invalid && formModel.get('ldnUrl').touched) {
+
+ @if (formModel.get('ldnUrl').errors['required']) {
+
+ {{ 'ldn-new-service.form.error.ldnurl' | translate }}
+
+ }
+ @if (formModel.get('ldnUrl').errors['ldnUrlAlreadyAssociated']) {
+
+ {{ 'ldn-new-service.form.error.ldnurl.ldnUrlAlreadyAssociated' | translate }}
+
+ }
-
- {{ 'ldn-new-service.form.error.ldnurl.ldnUrlAlreadyAssociated' | translate }}
+ }
+
+
+
+
+
{{ 'ldn-service-usesActorEmailId' | translate }}
+
+
+
+
+ {{ 'ldn-service-usesActorEmailId-description' | translate }}
-
-
- {{ 'ldn-new-service.form.label.inboundPattern' | translate }}
-
-
+ @if (areControlsInitialized) {
+
- {{ 'ldn-new-service.form.label.ItemFilter' | translate }}
+ {{ 'ldn-new-service.form.label.inboundPattern' | translate }}
-
-
{{ 'ldn-new-service.form.label.automatic' | translate }}
+ @if (formModel.get('notifyServiceInboundPatterns')['controls'][0]?.value?.pattern) {
+
+ {{ 'ldn-new-service.form.label.ItemFilter' | translate }}
+
+
+ {{ 'ldn-new-service.form.label.automatic' | translate }}
+
+ }
+
-
-
-
+ }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ }
{{ 'ldn-new-service.form.label.addPattern' | translate }}
+ class="add-pattern-link mb-2">{{ 'ldn-new-service.form.label.addPattern' | translate }}
-
-
+
+
+
-
- {{ 'service.overview.edit.body' | translate }}
-
-
- {{ 'service.overview.create.body' | translate }}
-
-
-
{{ 'process.overview.new' | translate }}
+ class="fas fa-plus pe-2">{{ 'process.overview.new' | translate }}
- 0"
- [collectionSize]="(ldnServicesRD$ | async)?.payload?.totalElements"
- [hideGear]="true"
- [hidePagerWhenSinglePage]="true"
- [paginationOptions]="pageConfig">
-
-
-
-
- {{ 'service.overview.table.name' | translate }}
- {{ 'service.overview.table.description' | translate }}
- {{ 'service.overview.table.status' | translate }}
- {{ 'service.overview.table.actions' | translate }}
-
-
-
-
- {{ ldnService.name }}
-
-
-
-
- {{ ldnService.description }}
-
-
-
-
-
-
- {{ ldnService.enabled ? ('ldn-service.overview.table.enabled' | translate) : ('ldn-service.overview.table.disabled' | translate) }}
-
-
-
-
-
-
-
-
0) {
+
+
+
+
+
+ {{ 'service.overview.table.name' | translate }}
+ {{ 'service.overview.table.description' | translate }}
+ {{ 'service.overview.table.status' | translate }}
+ {{ 'service.overview.table.actions' | translate }}
+
+
+
+ @for (ldnService of (ldnServicesRD$ | async)?.payload?.page; track ldnService) {
+
+ {{ ldnService.name }}
+
+
+
+
+ {{ ldnService.description }}
+
+
+
+
+
+
+ {{ ldnService.enabled ? ('ldn-service.overview.table.enabled' | translate) : ('ldn-service.overview.table.disabled' | translate) }}
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ }
+
+
+
+
+ }
@@ -72,8 +76,8 @@
{{'service.overview.delete.header' | translate }}
+ [attr.aria-label]="'ldn-service-overview-close-modal' | translate"
+ class="close" type="button">
×
@@ -84,12 +88,12 @@
{{ 'service.detail.delete.cancel' | translate }}
+ [attr.aria-label]="'ldn-service-overview-close-modal' | translate"
+ class="btn btn-outline-secondary me-2">{{ 'service.detail.delete.cancel' | translate }}
{{ 'service.overview.delete' | translate }}
+ class="btn btn-danger"
+ [attr.aria-label]="'ldn-service-overview-select-delete' | translate"
+ id="delete-confirm">{{ 'service.overview.delete' | translate }}
diff --git a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts
index cc2cba4704..59f21bb14a 100644
--- a/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts
+++ b/src/app/admin/admin-ldn-services/ldn-services-directory/ldn-services-directory.component.ts
@@ -1,8 +1,6 @@
import {
AsyncPipe,
NgClass,
- NgFor,
- NgIf,
} from '@angular/common';
import {
ChangeDetectionStrategy,
@@ -54,8 +52,6 @@ import { LdnService } from '../ldn-services-model/ldn-services.model';
styleUrls: ['./ldn-services-directory.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
imports: [
- NgIf,
- NgFor,
TranslateModule,
AsyncPipe,
PaginationComponent,
diff --git a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts
index 1497b618f0..5aed22ffb9 100644
--- a/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts
+++ b/src/app/admin/admin-ldn-services/ldn-services-model/ldn-services.model.ts
@@ -52,6 +52,9 @@ export class LdnService extends CacheableObject {
@autoserialize
enabled: boolean;
+ @autoserialize
+ usesActorEmailId: boolean;
+
@autoserialize
ldnUrl: string;
diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html
index b04e7132f1..b1d88de988 100644
--- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html
+++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html
@@ -1 +1 @@
-
+
diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
index ce8e846900..847910f447 100644
--- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
+++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
@@ -6,8 +6,9 @@ import {
waitForAsync,
} from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
+import { MockComponent } from 'ng-mocks';
-import { PublicationClaimComponent } from '../../../notifications/suggestion-targets/publication-claim/publication-claim.component';
+import { SuggestionSourcesComponent } from '../../../notifications/suggestions/sources/suggestion-sources.component';
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component';
describe('AdminNotificationsPublicationClaimPageComponent', () => {
@@ -20,17 +21,10 @@ describe('AdminNotificationsPublicationClaimPageComponent', () => {
CommonModule,
TranslateModule.forRoot(),
AdminNotificationsPublicationClaimPageComponent,
- ],
- providers: [
- AdminNotificationsPublicationClaimPageComponent,
+ MockComponent(SuggestionSourcesComponent),
],
schemas: [NO_ERRORS_SCHEMA],
- }).overrideComponent(AdminNotificationsPublicationClaimPageComponent, {
- remove: {
- imports: [PublicationClaimComponent],
- },
- })
- .compileComponents();
+ }).compileComponents();
}));
beforeEach(() => {
diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts
index 24af9350ee..2e92125a56 100644
--- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts
+++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts
@@ -1,14 +1,12 @@
import { Component } from '@angular/core';
-import { PublicationClaimComponent } from '../../../notifications/suggestion-targets/publication-claim/publication-claim.component';
+import { SuggestionSourcesComponent } from '../../../notifications/suggestions/sources/suggestion-sources.component';
@Component({
selector: 'ds-admin-notifications-publication-claim-page',
templateUrl: './admin-notifications-publication-claim-page.component.html',
styleUrls: ['./admin-notifications-publication-claim-page.component.scss'],
- imports: [
- PublicationClaimComponent,
- ],
+ imports: [ SuggestionSourcesComponent ],
standalone: true,
})
export class AdminNotificationsPublicationClaimPageComponent {
diff --git a/src/app/admin/admin-notifications/admin-notifications-routes.ts b/src/app/admin/admin-notifications/admin-notifications-routes.ts
index 43cfc2945a..309910d6bb 100644
--- a/src/app/admin/admin-notifications/admin-notifications-routes.ts
+++ b/src/app/admin/admin-notifications/admin-notifications-routes.ts
@@ -2,7 +2,8 @@ import { Route } from '@angular/router';
import { authenticatedGuard } from '../../core/auth/authenticated.guard';
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
-import { qualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
+import { sourcesBreadcrumbResolver } from '../../core/breadcrumbs/sources-breadcrumb.resolver';
+import { PublicationClaimComponent } from '../../notifications/suggestions/targets/publication-claim/publication-claim.component';
import { AdminNotificationsPublicationClaimPageResolver } from '../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service';
import { QualityAssuranceEventsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.component';
import { qualityAssuranceEventsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver';
@@ -33,13 +34,28 @@ export const ROUTES: Route[] = [
showBreadcrumbsFluid: false,
},
},
+ {
+ canActivate: [ authenticatedGuard ],
+ path: `${PUBLICATION_CLAIMS_PATH}/:sourceId`,
+ pathMatch: 'full',
+ component: PublicationClaimComponent,
+ resolve: {
+ breadcrumb: sourcesBreadcrumbResolver,
+ openaireQualityAssuranceEventsParams: AdminNotificationsPublicationClaimPageResolver,
+ },
+ data: {
+ title: 'admin.notifications.publicationclaim.page.title',
+ breadcrumbKey: 'admin.notifications.publicationclaim',
+ showBreadcrumbsFluid: false,
+ },
+ },
{
canActivate: [authenticatedGuard],
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
component: QualityAssuranceTopicsPageComponent,
pathMatch: 'full',
resolve: {
- breadcrumb: qualityAssuranceBreadcrumbResolver,
+ breadcrumb: sourcesBreadcrumbResolver,
openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver,
},
data: {
@@ -85,7 +101,7 @@ export const ROUTES: Route[] = [
component: QualityAssuranceEventsPageComponent,
pathMatch: 'full',
resolve: {
- breadcrumb: qualityAssuranceBreadcrumbResolver,
+ breadcrumb: sourcesBreadcrumbResolver,
openaireQualityAssuranceEventsParams: qualityAssuranceEventsPageResolver,
},
data: {
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.html b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.html
index 3adb7e857b..0e0183fe84 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.html
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.html
@@ -13,11 +13,13 @@
{{'admin.notify.dashboard.outbound-logs' | translate}}
-
-
-
+
+
+ @if ((notifyMetricsRows$ | async)?.length) {
+
+ }
+
-
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts
index 5508c18030..4fe93f4c86 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard.component.ts
@@ -1,7 +1,4 @@
-import {
- AsyncPipe,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
Inject,
@@ -46,7 +43,6 @@ import {
imports: [
AdminNotifyMetricsComponent,
RouterLink,
- NgIf,
TranslateModule,
AsyncPipe,
],
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.html b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.html
index 52d93cbb62..3760dc341d 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.html
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.html
@@ -5,12 +5,14 @@
-
+ @for (key of notifyMessageKeys; track key) {
+
{{ key + '.notify-detail-modal' | translate}}
{{'notify-detail-modal.' + notifyMessage[key] | translate: {default: notifyMessage[key] ?? "n/a" } }}
+ }
@@ -18,5 +20,7 @@
-
+ @if (isCoarMessageVisible) {
+
+ }
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts
index 717d24a9fc..1bd5eb9ec7 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-detail-modal/admin-notify-detail-modal.component.ts
@@ -1,7 +1,4 @@
-import {
- NgForOf,
- NgIf,
-} from '@angular/common';
+
import {
Component,
EventEmitter,
@@ -26,9 +23,7 @@ import { AdminNotifyMessage } from '../models/admin-notify-message.model';
],
standalone: true,
imports: [
- NgForOf,
TranslateModule,
- NgIf,
],
})
/**
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.html b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.html
index 57ce83934c..ce394175c3 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.html
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.html
@@ -3,10 +3,12 @@
{{((isInbound$ | async) ? 'admin.notify.dashboard.inbound' : 'admin.notify.dashboard.outbound') | translate}}
-
- {{ 'admin-notify-logs.' + (selectedSearchConfig$ | async) | translate}}
- ×
-
+ @if ((selectedSearchConfig$ | async) !== defaultConfiguration) {
+
+ {{ 'admin-notify-logs.' + (selectedSearchConfig$ | async) | translate}}
+ ×
+
+ }
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts
index 2c6164ecda..7aa1e63c56 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-logs/admin-notify-logs-result/admin-notify-logs-result.component.ts
@@ -1,7 +1,4 @@
-import {
- AsyncPipe,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
Inject,
@@ -39,7 +36,6 @@ import { ThemedSearchComponent } from '../../../../shared/search/themed-search.c
ThemedSearchComponent,
AsyncPipe,
TranslateModule,
- NgIf,
],
})
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.html b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.html
index 3257bdd5ba..5c7910284f 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.html
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.html
@@ -1,9 +1,13 @@
-
-
{{ row.title | translate }}
-
-
-
+@for (row of boxesConfig; track row) {
+
+
{{ row.title | translate }}
+
+ @for (box of row.boxes; track box) {
+
+
+
+ }
-
+}
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts
index af5345f512..6519826ddf 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.component.ts
@@ -1,4 +1,4 @@
-import { NgForOf } from '@angular/common';
+
import {
Component,
Input,
@@ -17,7 +17,6 @@ import { AdminNotifyMetricsRow } from './admin-notify-metrics.model';
imports: [
NotificationBoxComponent,
TranslateModule,
- NgForOf,
],
})
/**
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.html b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.html
index af540b094e..5c8360c726 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.html
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.html
@@ -11,41 +11,57 @@
-
-
- {{ message.queueLastStartTime | date:"YYYY/MM/d hh:mm:ss" }}
- n/a
-
-
-
-
- {{ message.relatedItem }}
-
-
- n/a
-
-
- {{ message.ldnService }}
- n/a
-
-
- {{ message.activityStreamType }}
-
-
- {{ 'notify-detail-modal.' + message.queueStatusLabel | translate }}
-
-
-
- {{ 'notify-message-result.detail' | translate }}
-
- {{ 'notify-message-result.reprocess' | translate }}
-
-
-
-
+ @for (message of (messagesSubject$ | async); track message) {
+
+
+ @if (message.queueLastStartTime) {
+ {{ message.queueLastStartTime | date:"YYYY/MM/d hh:mm:ss" }}
+ }
+ @if (!message.queueLastStartTime) {
+ n/a
+ }
+
+
+
+
+ @if (message.relatedItem) {
+ {{ message.relatedItem }}
+ }
+
+
+ @if (!message.relatedItem) {
+ n/a
+ }
+
+
+ @if (message.ldnService) {
+ {{ message.ldnService }}
+ }
+ @if (!message.ldnService) {
+ n/a
+ }
+
+
+ {{ message.activityStreamType }}
+
+
+ {{ 'notify-detail-modal.' + message.queueStatusLabel | translate }}
+
+
+
+ {{ 'notify-message-result.detail' | translate }}
+ @if (message.queueStatusLabel !== reprocessStatus && validStatusesForReprocess.includes(message.queueStatusLabel)) {
+
+ {{ 'notify-message-result.reprocess' | translate }}
+
+ }
+
+
+
+ }
diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts
index f1c8d9ead6..ece05c30bb 100644
--- a/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts
+++ b/src/app/admin/admin-notify-dashboard/admin-notify-search-result/admin-notify-search-result.component.ts
@@ -1,8 +1,6 @@
import {
AsyncPipe,
DatePipe,
- NgForOf,
- NgIf,
} from '@angular/common';
import {
Component,
@@ -42,8 +40,6 @@ import { AdminNotifyMessagesService } from '../services/admin-notify-messages.se
standalone: true,
imports: [
TranslateModule,
- NgForOf,
- NgIf,
DatePipe,
AsyncPipe,
TruncatableComponent,
diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html
index ccdfd3dfec..c063384389 100644
--- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html
+++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html
@@ -8,51 +8,62 @@
{{'admin.registries.bitstream-formats.create.new' | translate}}
-
0"
- [paginationOptions]="pageConfig"
- [collectionSize]="(bitstreamFormats$ | async)?.payload?.totalElements"
- [hideGear]="false"
- [hidePagerWhenSinglePage]="true">
-
-
+ @if ((bitstreamFormats$ | async)?.payload?.totalElements > 0) {
+
+
+
+ }
+ @if ((bitstreamFormats$ | async)?.payload?.totalElements === 0) {
+
+ {{'admin.registries.bitstream-formats.no-items' | translate}}
-
-
- {{'admin.registries.bitstream-formats.no-items' | translate}}
-
+ }
- 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}
- 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}
+ @if ((bitstreamFormats$ | async)?.payload?.page?.length > 0) {
+ {{'admin.registries.bitstream-formats.table.deselect-all' | translate}}
+ }
+ @if ((bitstreamFormats$ | async)?.payload?.page?.length > 0) {
+ {{'admin.registries.bitstream-formats.table.delete' | translate}}
+ }
diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
index 74869670c5..bc0f76ea17 100644
--- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
+++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -1,8 +1,4 @@
-import {
- AsyncPipe,
- NgForOf,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
OnDestroy,
@@ -41,12 +37,10 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
selector: 'ds-bitstream-formats',
templateUrl: './bitstream-formats.component.html',
imports: [
- NgIf,
AsyncPipe,
RouterLink,
TranslateModule,
PaginationComponent,
- NgForOf,
],
standalone: true,
})
diff --git a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.html b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.html
index be6ebf2599..7403623747 100644
--- a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.html
+++ b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.html
@@ -1,3 +1,5 @@
-
\ No newline at end of file
+@if (formModel) {
+
+}
\ No newline at end of file
diff --git a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
index 37ae0d1dc0..59fa50ee5b 100644
--- a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
+++ b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
@@ -1,4 +1,4 @@
-import { NgIf } from '@angular/common';
+
import {
Component,
EventEmitter,
@@ -35,7 +35,6 @@ import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-p
templateUrl: './format-form.component.html',
imports: [
FormComponent,
- NgIf,
],
standalone: true,
})
@@ -64,7 +63,7 @@ export class FormatFormComponent implements OnInit {
*/
arrayElementLayout: DynamicFormControlLayout = {
grid: {
- group: 'form-row',
+ group: 'row',
},
};
diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html
index 8b3c6fe972..73072b6104 100644
--- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html
+++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html
@@ -1,60 +1,65 @@
-
-
diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts
index be1239ab95..8e5adeddf7 100644
--- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts
+++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts
@@ -1,8 +1,6 @@
import {
AsyncPipe,
NgClass,
- NgForOf,
- NgIf,
} from '@angular/common';
import {
Component,
@@ -49,8 +47,6 @@ import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-sch
TranslateModule,
AsyncPipe,
PaginationComponent,
- NgIf,
- NgForOf,
NgClass,
RouterLink,
],
diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html
index 15cc81d9ca..343e107e01 100644
--- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html
+++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html
@@ -1,18 +1,16 @@
-
+@if (activeMetadataSchema$ | async) {
+
{{messagePrefix + '.edit' | translate}}
+} @else {
+
{{messagePrefix + '.create' | translate}}
+}
-
- {{messagePrefix + '.create' | translate}}
-
-
- {{messagePrefix + '.edit' | translate}}
-
+ [formModel]="formModel"
+ [formGroup]="formGroup"
+ [formLayout]="formLayout"
+ (cancel)="onCancel()"
+ (submitForm)="onSubmit()">
diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
index c58c4bef10..139f711920 100644
--- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
+++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts
@@ -1,7 +1,4 @@
-import {
- AsyncPipe,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
EventEmitter,
@@ -39,7 +36,6 @@ import { FormComponent } from '../../../../shared/form/form.component';
selector: 'ds-metadata-schema-form',
templateUrl: './metadata-schema-form.component.html',
imports: [
- NgIf,
AsyncPipe,
TranslateModule,
FormComponent,
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
index 44b6bfb697..c799d87b26 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.html
@@ -1,18 +1,16 @@
-
+@if (registryService.getActiveMetadataField() | async) {
+
{{messagePrefix + '.edit' | translate}}
+} @else {
+
{{messagePrefix + '.create' | translate}}
+}
-
- {{messagePrefix + '.create' | translate}}
-
-
- {{messagePrefix + '.edit' | translate}}
-
+ [formModel]="formModel"
+ [formLayout]="formLayout"
+ [formGroup]="formGroup"
+ (cancel)="onCancel()"
+ (submit)="onSubmit()">
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
index bfadd018ef..63f93961be 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts
@@ -1,7 +1,4 @@
-import {
- AsyncPipe,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
EventEmitter,
@@ -35,7 +32,6 @@ import { FormComponent } from '../../../../shared/form/form.component';
selector: 'ds-metadata-field-form',
templateUrl: './metadata-field-form.component.html',
imports: [
- NgIf,
FormComponent,
TranslateModule,
AsyncPipe,
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html
index 288266fb75..438b37d3aa 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html
@@ -8,52 +8,59 @@
+ (submitForm)="forceUpdateFields()">
{{'admin.registries.schema.fields.head' | translate}}
- 0"
- [paginationOptions]="config"
- [collectionSize]="fields?.totalElements"
- [hideGear]="false"
- [hidePagerWhenSinglePage]="true">
-
-
+ @if (fields?.totalElements > 0) {
+
+
+
+ }
-
- {{'admin.registries.schema.fields.no-items' | translate}}
-
+ @if (fields?.totalElements === 0) {
+
+ {{'admin.registries.schema.fields.no-items' | translate}}
+
+ }
{{'admin.registries.schema.return' | translate}}
- 0" type="submit" class="btn btn-danger float-right" (click)="deleteFields()">{{'admin.registries.schema.fields.table.delete' | translate}}
+ @if (fields?.page?.length > 0) {
+ {{'admin.registries.schema.fields.table.delete' | translate}}
+ }
diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts
index ec5d6b4cb0..52f927073f 100644
--- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts
+++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts
@@ -1,8 +1,6 @@
import {
AsyncPipe,
NgClass,
- NgForOf,
- NgIf,
} from '@angular/common';
import {
Component,
@@ -59,8 +57,6 @@ import { MetadataFieldFormComponent } from './metadata-field-form/metadata-field
MetadataFieldFormComponent,
TranslateModule,
PaginationComponent,
- NgIf,
- NgForOf,
NgClass,
RouterLink,
],
diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.html b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.html
index 5199a115a6..c696fa23ba 100644
--- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.html
+++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.html
@@ -1,64 +1,72 @@
-
diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts
index dff6445225..ae4df271ae 100644
--- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts
+++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.spec.ts
@@ -1,4 +1,8 @@
-import { HttpClientTestingModule } from '@angular/common/http/testing';
+import {
+ provideHttpClient,
+ withInterceptorsFromDi,
+} from '@angular/common/http';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
ComponentFixture,
@@ -39,22 +43,21 @@ describe('FiltersComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [
- NgbAccordionModule,
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [NgbAccordionModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock,
},
}),
- HttpClientTestingModule,
- FilteredCollectionsComponent,
- ],
+ FilteredCollectionsComponent],
providers: [
FormBuilder,
DspaceRestService,
+ provideHttpClient(withInterceptorsFromDi()),
+ provideHttpClientTesting(),
],
- schemas: [NO_ERRORS_SCHEMA],
});
}));
diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts
index e1f54bd8d3..85924dd82f 100644
--- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts
+++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts
@@ -1,7 +1,4 @@
-import {
- KeyValuePipe,
- NgForOf,
-} from '@angular/common';
+import { KeyValuePipe } from '@angular/common';
import {
Component,
OnInit,
@@ -37,7 +34,6 @@ import { FilteredCollections } from './filtered-collections.model';
NgbAccordionModule,
FiltersComponent,
KeyValuePipe,
- NgForOf,
],
standalone: true,
})
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.html b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.html
new file mode 100644
index 0000000000..a8f5463ce1
--- /dev/null
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.html
@@ -0,0 +1,8 @@
+@if (shouldShowButton$ | async) {
+
+
+
+}
\ No newline at end of file
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.scss b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.scss
new file mode 100644
index 0000000000..4b0ab3c44a
--- /dev/null
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.scss
@@ -0,0 +1,4 @@
+.export-button {
+ background: var(--ds-admin-sidebar-bg);
+ border-color: var(--ds-admin-sidebar-bg);
+}
\ No newline at end of file
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.spec.ts b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.spec.ts
new file mode 100644
index 0000000000..d9627dff70
--- /dev/null
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.spec.ts
@@ -0,0 +1,194 @@
+import {
+ ComponentFixture,
+ TestBed,
+ waitForAsync,
+} from '@angular/core/testing';
+import {
+ FormControl,
+ FormGroup,
+} from '@angular/forms';
+import { By } from '@angular/platform-browser';
+import { Router } from '@angular/router';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { TranslateModule } from '@ngx-translate/core';
+import { of as observableOf } from 'rxjs';
+
+import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
+import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
+import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
+import { Process } from '../../../../process-page/processes/process.model';
+import { Script } from '../../../../process-page/scripts/script.model';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import {
+ createFailedRemoteDataObject$,
+ createSuccessfulRemoteDataObject$,
+} from '../../../../shared/remote-data.utils';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { FiltersComponent } from '../../filters-section/filters-section.component';
+import { OptionVO } from '../option-vo.model';
+import { QueryPredicate } from '../query-predicate.model';
+import { FilteredItemsExportCsvComponent } from './filtered-items-export-csv.component';
+
+describe('FilteredItemsExportCsvComponent', () => {
+ let component: FilteredItemsExportCsvComponent;
+ let fixture: ComponentFixture
;
+
+ let scriptDataService: ScriptDataService;
+ let authorizationDataService: AuthorizationDataService;
+ let notificationsService;
+ let router;
+
+ const script = Object.assign(new Script(), { id: 'metadata-export-filtered-items-report', name: 'metadata-export-filtered-items-report' });
+ const process = Object.assign(new Process(), { processId: 5, scriptName: 'metadata-export-filtered-items-report' });
+
+ const params = new FormGroup({
+ collections: new FormControl([OptionVO.collection('1', 'coll1')]),
+ queryPredicates: new FormControl([QueryPredicate.of('name', 'equals', 'coll1')]),
+ filters: new FormControl([FiltersComponent.getFilter('is_item')]),
+ });
+
+ const emptyParams = new FormGroup({
+ collections: new FormControl([]),
+ queryPredicates: new FormControl([]),
+ filters: new FormControl([]),
+ });
+
+ function initBeforeEachAsync() {
+ scriptDataService = jasmine.createSpyObj('scriptDataService', {
+ findById: createSuccessfulRemoteDataObject$(script),
+ invoke: createSuccessfulRemoteDataObject$(process),
+ });
+ authorizationDataService = jasmine.createSpyObj('authorizationService', {
+ isAuthorized: observableOf(true),
+ });
+
+ notificationsService = new NotificationsServiceStub();
+
+ router = jasmine.createSpyObj('authorizationService', ['navigateByUrl']);
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), NgbModule, FilteredItemsExportCsvComponent],
+ providers: [
+ { provide: ScriptDataService, useValue: scriptDataService },
+ { provide: AuthorizationDataService, useValue: authorizationDataService },
+ { provide: NotificationsService, useValue: notificationsService },
+ { provide: Router, useValue: router },
+ ],
+ }).compileComponents();
+ }
+
+ function initBeforeEach() {
+ fixture = TestBed.createComponent(FilteredItemsExportCsvComponent);
+ component = fixture.componentInstance;
+ component.reportParams = params;
+ fixture.detectChanges();
+ }
+
+ describe('init', () => {
+ describe('comp', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should init the comp', () => {
+ expect(component).toBeTruthy();
+ });
+ });
+ describe('when the user is an admin and the metadata-export-filtered-items-report script is present ', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should add the button', () => {
+ const debugElement = fixture.debugElement.query(By.css('button.export-button'));
+ expect(debugElement).toBeDefined();
+ });
+ });
+ describe('when the user is not an admin', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ (authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should not add the button', () => {
+ const debugElement = fixture.debugElement.query(By.css('button.export-button'));
+ expect(debugElement).toBeNull();
+ });
+ });
+ describe('when the metadata-export-filtered-items-report script is not present', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ (scriptDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not found', 404));
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should should not add the button', () => {
+ const debugElement = fixture.debugElement.query(By.css('button.export-button'));
+ expect(debugElement).toBeNull();
+ });
+ });
+ });
+ describe('export', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should call the invoke script method with the correct parameters', () => {
+ // Parameterized export
+ component.export();
+ expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-filtered-items-report',
+ [
+ { name: '-c', value: params.value.collections[0].id },
+ { name: '-qp', value: QueryPredicate.toString(params.value.queryPredicates[0]) },
+ { name: '-f', value: FiltersComponent.toQueryString(params.value.filters) },
+ ], []);
+
+ fixture.detectChanges();
+
+ // Non-parameterized export
+ component.reportParams = emptyParams;
+ fixture.detectChanges();
+ component.export();
+ expect(scriptDataService.invoke).toHaveBeenCalledWith('metadata-export-filtered-items-report', [], []);
+
+ });
+ it('should show a success message when the script was invoked successfully and redirect to the corresponding process page', () => {
+ component.export();
+
+ expect(notificationsService.success).toHaveBeenCalled();
+ expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessDetailRoute(process.processId));
+ });
+ it('should show an error message when the script was not invoked successfully and stay on the current page', () => {
+ (scriptDataService.invoke as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Error', 500));
+
+ component.export();
+
+ expect(notificationsService.error).toHaveBeenCalled();
+ expect(router.navigateByUrl).not.toHaveBeenCalled();
+ });
+ });
+ describe('clicking the button', () => {
+ beforeEach(waitForAsync(() => {
+ initBeforeEachAsync();
+ }));
+ beforeEach(() => {
+ initBeforeEach();
+ });
+ it('should trigger the export function', () => {
+ spyOn(component, 'export');
+
+ const debugElement = fixture.debugElement.query(By.css('button.export-button'));
+ debugElement.triggerEventHandler('click', null);
+
+ expect(component.export).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.ts
new file mode 100644
index 0000000000..50a0ca32b7
--- /dev/null
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items-export-csv/filtered-items-export-csv.component.ts
@@ -0,0 +1,123 @@
+import { AsyncPipe } from '@angular/common';
+import {
+ Component,
+ Input,
+ OnInit,
+} from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { Router } from '@angular/router';
+import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
+import {
+ TranslateModule,
+ TranslateService,
+} from '@ngx-translate/core';
+import {
+ combineLatest as observableCombineLatest,
+ Observable,
+} from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
+import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
+import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
+import { Process } from '../../../../process-page/processes/process.model';
+import { hasValue } from '../../../../shared/empty.util';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { FiltersComponent } from '../../filters-section/filters-section.component';
+import { OptionVO } from '../option-vo.model';
+import { QueryPredicate } from '../query-predicate.model';
+
+@Component({
+ selector: 'ds-filtered-items-export-csv',
+ styleUrls: ['./filtered-items-export-csv.component.scss'],
+ templateUrl: './filtered-items-export-csv.component.html',
+ standalone: true,
+ imports: [NgbTooltipModule, AsyncPipe, TranslateModule],
+})
+/**
+ * Display a button to export the MetadataQuery (aka Filtered Items) Report results as csv
+ */
+export class FilteredItemsExportCsvComponent implements OnInit {
+
+ /**
+ * The current configuration of the search
+ */
+ @Input() reportParams: FormGroup;
+
+ /**
+ * Observable used to determine whether the button should be shown
+ */
+ shouldShowButton$: Observable;
+
+ /**
+ * The message key used for the tooltip of the button
+ */
+ tooltipMsg = 'metadata-export-filtered-items.tooltip';
+
+ constructor(private scriptDataService: ScriptDataService,
+ private authorizationDataService: AuthorizationDataService,
+ private notificationsService: NotificationsService,
+ private translateService: TranslateService,
+ private router: Router,
+ ) {
+ }
+
+ static csvExportEnabled(scriptDataService: ScriptDataService, authorizationDataService: AuthorizationDataService): Observable {
+ const scriptExists$ = scriptDataService.findById('metadata-export-filtered-items-report').pipe(
+ getFirstCompletedRemoteData(),
+ map((rd) => rd.isSuccess && hasValue(rd.payload)),
+ );
+
+ const isAuthorized$ = authorizationDataService.isAuthorized(FeatureID.AdministratorOf);
+
+ return observableCombineLatest([scriptExists$, isAuthorized$]).pipe(
+ map(([scriptExists, isAuthorized]: [boolean, boolean]) => scriptExists && isAuthorized),
+ );
+ }
+
+ ngOnInit(): void {
+ this.shouldShowButton$ = FilteredItemsExportCsvComponent.csvExportEnabled(this.scriptDataService, this.authorizationDataService);
+ }
+
+ /**
+ * Start the export of the items based on the selected parameters
+ */
+ export() {
+ const parameters = [];
+ const colls = this.reportParams.value.collections || [];
+ for (let i = 0; i < colls.length; i++) {
+ if (colls[i]) {
+ parameters.push({ name: '-c', value: OptionVO.toString(colls[i]) });
+ }
+ }
+
+ const preds = this.reportParams.value.queryPredicates || [];
+ for (let i = 0; i < preds.length; i++) {
+ const field = preds[i].field;
+ const op = preds[i].operator;
+ if (field && op) {
+ parameters.push({ name: '-qp', value: QueryPredicate.toString(preds[i]) });
+ }
+ }
+
+ const filters = FiltersComponent.toQueryString(this.reportParams.value.filters) || [];
+ if (filters.length > 0) {
+ parameters.push({ name: '-f', value: filters });
+ }
+
+ this.scriptDataService.invoke('metadata-export-filtered-items-report', parameters, []).pipe(
+ getFirstCompletedRemoteData(),
+ ).subscribe((rd: RemoteData) => {
+ if (rd.hasSucceeded) {
+ this.notificationsService.success(this.translateService.get('metadata-export-filtered-items.submit.success'));
+ this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
+ } else {
+ this.notificationsService.error(this.translateService.get('metadata-export-filtered-items.submit.error'));
+ }
+ });
+ }
+
+}
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items-model.ts b/src/app/admin/admin-reports/filtered-items/filtered-items-model.ts
index b5bc55f6ff..b12a7967c8 100644
--- a/src/app/admin/admin-reports/filtered-items/filtered-items-model.ts
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items-model.ts
@@ -1,8 +1,10 @@
import { Item } from 'src/app/core/shared/item.model';
+import { Collection } from '../../../core/shared/collection.model';
+
export class FilteredItems {
- public items: Item[] = [];
+ public items: FilteredItem[] = [];
public itemCount: number;
public clear() {
@@ -21,3 +23,8 @@ export class FilteredItems {
}
}
+
+export interface FilteredItem extends Omit- {
+ index: number;
+ owningCollection?: Collection;
+}
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.html b/src/app/admin/admin-reports/filtered-items/filtered-items.component.html
index a765c4a190..dd3f45c216 100644
--- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.html
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.html
@@ -1,175 +1,210 @@
-
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.scss b/src/app/admin/admin-reports/filtered-items/filtered-items.component.scss
index 73ce5275e5..15e8b54bc7 100644
--- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.scss
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.scss
@@ -1,3 +1,10 @@
.num {
text-align: center;
}
+
+.warning {
+ color: red;
+ font-style: italic;
+ text-align: center;
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts
index 04ee4894ec..1daea4168e 100644
--- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts
+++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts
@@ -1,8 +1,4 @@
-import {
- AsyncPipe,
- NgForOf,
- NgIf,
-} from '@angular/common';
+import { AsyncPipe } from '@angular/common';
import {
Component,
OnInit,
@@ -24,13 +20,16 @@ import {
TranslateService,
} from '@ngx-translate/core';
import {
+ BehaviorSubject,
map,
Observable,
} from 'rxjs';
import { CollectionDataService } from 'src/app/core/data/collection-data.service';
import { CommunityDataService } from 'src/app/core/data/community-data.service';
+import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
import { MetadataFieldDataService } from 'src/app/core/data/metadata-field-data.service';
import { MetadataSchemaDataService } from 'src/app/core/data/metadata-schema-data.service';
+import { ScriptDataService } from 'src/app/core/data/processes/script-data.service';
import { RestRequestMethod } from 'src/app/core/data/rest-request-method';
import { DspaceRestService } from 'src/app/core/dspace-rest/dspace-rest.service';
import { RawRestResponse } from 'src/app/core/dspace-rest/raw-rest-response.model';
@@ -38,14 +37,18 @@ import { MetadataField } from 'src/app/core/metadata/metadata-field.model';
import { MetadataSchema } from 'src/app/core/metadata/metadata-schema.model';
import { Collection } from 'src/app/core/shared/collection.model';
import { Community } from 'src/app/core/shared/community.model';
-import { Item } from 'src/app/core/shared/item.model';
import { getFirstSucceededRemoteListPayload } from 'src/app/core/shared/operators';
import { isEmpty } from 'src/app/shared/empty.util';
+import { ThemedLoadingComponent } from 'src/app/shared/loading/themed-loading.component';
import { environment } from 'src/environments/environment';
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
import { FiltersComponent } from '../filters-section/filters-section.component';
-import { FilteredItems } from './filtered-items-model';
+import { FilteredItemsExportCsvComponent } from './filtered-items-export-csv/filtered-items-export-csv.component';
+import {
+ FilteredItem,
+ FilteredItems,
+} from './filtered-items-model';
import { OptionVO } from './option-vo.model';
import { PresetQuery } from './preset-query.model';
import { QueryPredicate } from './query-predicate.model';
@@ -62,16 +65,21 @@ import { QueryPredicate } from './query-predicate.model';
NgbAccordionModule,
TranslateModule,
AsyncPipe,
- NgIf,
- NgForOf,
FiltersComponent,
BtnDisabledDirective,
+ FilteredItemsExportCsvComponent,
+ ThemedLoadingComponent,
],
standalone: true,
})
export class FilteredItemsComponent implements OnInit {
collections: OptionVO[];
+ /**
+ * A Boolean representing if loading the list of collections is pending
+ */
+ loadingCollections$: BehaviorSubject
= new BehaviorSubject(false);
+
presetQueries: PresetQuery[];
metadataFields: OptionVO[];
metadataFieldsWithAny: OptionVO[];
@@ -81,8 +89,12 @@ export class FilteredItemsComponent implements OnInit {
queryForm: FormGroup;
currentPage = 0;
results: FilteredItems = new FilteredItems();
- results$: Observable- ;
+ results$: Observable
;
@ViewChild('acc') accordionComponent: NgbAccordion;
+ /**
+ * Observable used to determine whether CSV export is enabled
+ */
+ csvExportEnabled$: Observable;
constructor(
private communityService: CommunityDataService,
@@ -90,6 +102,8 @@ export class FilteredItemsComponent implements OnInit {
private metadataSchemaService: MetadataSchemaDataService,
private metadataFieldService: MetadataFieldDataService,
private translateService: TranslateService,
+ private scriptDataService: ScriptDataService,
+ private authorizationDataService: AuthorizationDataService,
private formBuilder: FormBuilder,
private restService: DspaceRestService) {}
@@ -104,6 +118,8 @@ export class FilteredItemsComponent implements OnInit {
new QueryPredicate().toFormGroup(this.formBuilder),
];
+ this.csvExportEnabled$ = FilteredItemsExportCsvComponent.csvExportEnabled(this.scriptDataService, this.authorizationDataService);
+
this.queryForm = this.formBuilder.group({
collections: this.formBuilder.control([''], []),
presetQuery: this.formBuilder.control('new', []),
@@ -115,6 +131,7 @@ export class FilteredItemsComponent implements OnInit {
}
loadCollections(): void {
+ this.loadingCollections$.next(true);
this.collections = [];
const wholeRepo$ = this.translateService.stream('admin.reports.items.wholeRepo');
this.collections.push(OptionVO.collectionLoc('', wholeRepo$));
@@ -136,6 +153,7 @@ export class FilteredItemsComponent implements OnInit {
const collVO = OptionVO.collection(collection.uuid, '–' + collection.name);
this.collections.push(collVO);
});
+ this.loadingCollections$.next(false);
},
);
});
@@ -171,10 +189,10 @@ export class FilteredItemsComponent implements OnInit {
QueryPredicate.of('dc.description.provenance', QueryPredicate.DOES_NOT_MATCH, '^.*No\. of bitstreams(.|\r|\n|\r\n)*\.(PDF|pdf|DOC|doc|PPT|ppt|DOCX|docx|PPTX|pptx).*$'),
]),
PresetQuery.of('q9', 'admin.reports.items.preset.hasEmptyMetadata', [
- QueryPredicate.of('*', QueryPredicate.MATCHES, '^\s*$'),
+ QueryPredicate.of('*', QueryPredicate.MATCHES, '^\\s*$'),
]),
PresetQuery.of('q10', 'admin.reports.items.preset.hasUnbreakingDataInDescription', [
- QueryPredicate.of('dc.description.*', QueryPredicate.MATCHES, '^.*[^\s]{50,}.*$'),
+ QueryPredicate.of('dc.description.*', QueryPredicate.MATCHES, '^.*(\\S){50,}.*$'),
]),
PresetQuery.of('q12', 'admin.reports.items.preset.hasXmlEntityInMetadata', [
QueryPredicate.of('*', QueryPredicate.MATCHES, '^.*.*$'),
@@ -348,13 +366,8 @@ export class FilteredItemsComponent implements OnInit {
const preds = this.queryForm.value.queryPredicates;
for (let i = 0; i < preds.length; i++) {
- const field = preds[i].field;
- const op = preds[i].operator;
- const value = preds[i].value;
- params += `&queryPredicates=${field}:${op}`;
- if (value) {
- params += `:${value}`;
- }
+ const pred = encodeURIComponent(QueryPredicate.toString(preds[i]));
+ params += `&queryPredicates=${pred}`;
}
const filters = FiltersComponent.toQueryString(this.queryForm.value.filters);
diff --git a/src/app/admin/admin-reports/filtered-items/option-vo.model.ts b/src/app/admin/admin-reports/filtered-items/option-vo.model.ts
index 56334b041f..a598fb9a3b 100644
--- a/src/app/admin/admin-reports/filtered-items/option-vo.model.ts
+++ b/src/app/admin/admin-reports/filtered-items/option-vo.model.ts
@@ -9,6 +9,7 @@ export class OptionVO {
id: string;
name$: Observable;
disabled = false;
+ isDefault?: boolean;
static collection(id: string, name: string, disabled: boolean = false): OptionVO {
const opt = new OptionVO();
@@ -45,6 +46,16 @@ export class OptionVO {
subscriber.next(value);
subscriber.complete();
});
-
}
+
+ static toString(obj: any): string {
+ if (obj) {
+ if (obj instanceof OptionVO && obj.id) {
+ return obj.id;
+ }
+ return obj as string;
+ }
+ return '';
+ }
+
}
diff --git a/src/app/admin/admin-reports/filtered-items/preset-query.model.ts b/src/app/admin/admin-reports/filtered-items/preset-query.model.ts
index 213819b70e..9f0fb01ce3 100644
--- a/src/app/admin/admin-reports/filtered-items/preset-query.model.ts
+++ b/src/app/admin/admin-reports/filtered-items/preset-query.model.ts
@@ -5,6 +5,7 @@ export class PresetQuery {
id: string;
label: string;
predicates: QueryPredicate[];
+ isDefault?: boolean;
static of(id: string, label: string, predicates: QueryPredicate[]) {
const query = new PresetQuery();
diff --git a/src/app/admin/admin-reports/filtered-items/query-predicate.model.ts b/src/app/admin/admin-reports/filtered-items/query-predicate.model.ts
index 1c12d72e27..1c91bfa744 100644
--- a/src/app/admin/admin-reports/filtered-items/query-predicate.model.ts
+++ b/src/app/admin/admin-reports/filtered-items/query-predicate.model.ts
@@ -29,6 +29,13 @@ export class QueryPredicate {
return pred;
}
+ static toString(pred: QueryPredicate): string {
+ if (pred.value) {
+ return `${pred.field}:${pred.operator}:${pred.value}`;
+ }
+ return `${pred.field}:${pred.operator}`;
+ }
+
toFormGroup(formBuilder: FormBuilder): FormGroup {
return formBuilder.group({
field: new FormControl(this.field),
diff --git a/src/app/admin/admin-reports/filters-section/filters-section.component.html b/src/app/admin/admin-reports/filters-section/filters-section.component.html
index 1e7856f09c..88e4335506 100644
--- a/src/app/admin/admin-reports/filters-section/filters-section.component.html
+++ b/src/app/admin/admin-reports/filters-section/filters-section.component.html
@@ -1,19 +1,23 @@
-
-
-
- {{"admin.reports.commons.filters.select_all" | translate}}
-
- {{"admin.reports.commons.filters.deselect_all" | translate}}
-
-
-
+
+
+
+ {{"admin.reports.commons.filters.select_all" | translate}}
+
+ {{"admin.reports.commons.filters.deselect_all" | translate}}
+
+
+
-
+@for (group of allFilters(); track group) {
+
{{group.key | translate}}
-
-
{{filter.key | translate}}
+ @for (filter of group.filters; track filter) {
+
+ {{filter.key | translate}}
+ }
-
+
+}
diff --git a/src/app/admin/admin-reports/filters-section/filters-section.component.scss b/src/app/admin/admin-reports/filters-section/filters-section.component.scss
index e69de29bb2..58d3a12f1b 100644
--- a/src/app/admin/admin-reports/filters-section/filters-section.component.scss
+++ b/src/app/admin/admin-reports/filters-section/filters-section.component.scss
@@ -0,0 +1,8 @@
+.col-6 > label.col-11.align-middle {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+fieldset.row-cols-2 > legend {
+ float: none !important;
+}
diff --git a/src/app/admin/admin-reports/filters-section/filters-section.component.ts b/src/app/admin/admin-reports/filters-section/filters-section.component.ts
index 85b7932ab4..24bd1103c9 100644
--- a/src/app/admin/admin-reports/filters-section/filters-section.component.ts
+++ b/src/app/admin/admin-reports/filters-section/filters-section.component.ts
@@ -1,4 +1,4 @@
-import { NgForOf } from '@angular/common';
+
import {
Component,
Input,
@@ -23,7 +23,6 @@ import { FilterGroup } from './filter-group.model';
templateUrl: './filters-section.component.html',
styleUrls: ['./filters-section.component.scss'],
imports: [
- NgForOf,
ReactiveFormsModule,
TranslateModule,
],
diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts
index 3eb8d9caf7..7a6b58dea2 100644
--- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts
+++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts
@@ -10,7 +10,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { AuthService } from '../../../../../core/auth/auth.service';
-import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
import { RemoteData } from '../../../../../core/data/remote-data';
@@ -22,7 +21,6 @@ import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { mockTruncatableService } from '../../../../../shared/mocks/mock-trucatable.service';
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
-import { AccessStatusObject } from '../../../../../shared/object-collection/shared/badges/access-status-badge/access-status.model';
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
@@ -44,12 +42,6 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
},
};
- const mockAccessStatusDataService = {
- findAccessStatusFor(item: Item): Observable
> {
- return createSuccessfulRemoteDataObject$(new AccessStatusObject());
- },
- };
-
const mockThemeService = getMockThemeService();
function init() {
@@ -74,7 +66,6 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
{ provide: TruncatableService, useValue: mockTruncatableService },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: ThemeService, useValue: mockThemeService },
- { provide: AccessStatusDataService, useValue: mockAccessStatusDataService },
{ provide: AuthService, useClass: AuthServiceStub },
{ provide: FileService, useClass: FileServiceStub },
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
diff --git a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html
index ba4ab15363..5028070c90 100644
--- a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html
+++ b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.html
@@ -1,30 +1,52 @@
diff --git a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts
index 89d51481d7..681b02ce0e 100644
--- a/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts
+++ b/src/app/admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component.ts
@@ -1,7 +1,4 @@
-import {
- NgClass,
- NgIf,
-} from '@angular/common';
+import { NgClass } from '@angular/common';
import {
Component,
Input,
@@ -26,7 +23,7 @@ import { getItemEditRoute } from '../../../item-page/item-page-routing-paths';
styleUrls: ['./item-admin-search-result-actions.component.scss'],
templateUrl: './item-admin-search-result-actions.component.html',
standalone: true,
- imports: [NgClass, RouterLink, NgIf, TranslateModule],
+ imports: [NgClass, RouterLink, TranslateModule],
})
/**
* The component for displaying the actions for a list element for an item search result on the admin search page
diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html
index 30a7a3353b..8d0ac83c32 100644
--- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html
+++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html
@@ -3,7 +3,7 @@
[ngClass]="{ disabled: isDisabled }"
role="menuitem"
[attr.aria-disabled]="isDisabled"
- [attr.aria-labelledby]="adminMenuSectionTitleId(section.id)"
+ [attr.aria-labelledby]="adminMenuSectionTitleAccessibilityHandle(section)"
[routerLink]="itemModel.link"
(keyup.space)="navigate($event)"
(keyup.enter)="navigate($event)"
@@ -14,7 +14,7 @@