mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
refactor, add detail mapping, add missing translation, optimize modal
This commit is contained in:
@@ -34,11 +34,27 @@ import {
|
|||||||
path: 'inbound',
|
path: 'inbound',
|
||||||
component: AdminNotifyIncomingComponent,
|
component: AdminNotifyIncomingComponent,
|
||||||
canActivate: [SiteAdministratorGuard],
|
canActivate: [SiteAdministratorGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'admin.notify.dashboard.page.title',
|
||||||
|
breadcrumbKey: 'admin.notify.dashboard',
|
||||||
|
showBreadcrumbsFluid: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'outbound',
|
path: 'outbound',
|
||||||
component: AdminNotifyOutgoingComponent,
|
component: AdminNotifyOutgoingComponent,
|
||||||
canActivate: [SiteAdministratorGuard],
|
canActivate: [SiteAdministratorGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'admin.notify.dashboard.page.title',
|
||||||
|
breadcrumbKey: 'admin.notify.dashboard',
|
||||||
|
showBreadcrumbsFluid: false
|
||||||
|
},
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -8,14 +8,13 @@ import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-i
|
|||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { SearchModule } from '../../shared/search/search.module';
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
import { SearchPageModule } from '../../search-page/search-page.module';
|
import { SearchPageModule } from '../../search-page/search-page.module';
|
||||||
import { AdminNotifyIncomingSearchResultComponent } from './admin-notify-search-result/incoming/admin-notify-incoming-search-result.component';
|
|
||||||
import {
|
import {
|
||||||
AdminNotifyOutgoingComponent
|
AdminNotifyOutgoingComponent
|
||||||
} from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component';
|
} from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component';
|
||||||
import { AdminNotifyDetailModalComponent } from './admin-notify-detail-modal/admin-notify-detail-modal.component';
|
import { AdminNotifyDetailModalComponent } from './admin-notify-detail-modal/admin-notify-detail-modal.component';
|
||||||
import {
|
import {
|
||||||
AdminNotifyOutgoingSearchResultComponent
|
AdminNotifySearchResultComponent
|
||||||
} from "./admin-notify-search-result/outgoing/admin-notify-outgoing-search-result.component";
|
} from "./admin-notify-search-result/admin-notify-search-result.component";
|
||||||
import { AdminNotifyMessagesService } from "./services/admin-notify-messages.service";
|
import { AdminNotifyMessagesService } from "./services/admin-notify-messages.service";
|
||||||
|
|
||||||
|
|
||||||
@@ -36,8 +35,7 @@ import { AdminNotifyMessagesService } from "./services/admin-notify-messages.ser
|
|||||||
AdminNotifyMetricsComponent,
|
AdminNotifyMetricsComponent,
|
||||||
AdminNotifyIncomingComponent,
|
AdminNotifyIncomingComponent,
|
||||||
AdminNotifyOutgoingComponent,
|
AdminNotifyOutgoingComponent,
|
||||||
AdminNotifyIncomingSearchResultComponent,
|
AdminNotifySearchResultComponent,
|
||||||
AdminNotifyOutgoingSearchResultComponent,
|
|
||||||
AdminNotifyDetailModalComponent
|
AdminNotifyDetailModalComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">Message Detail</h4>
|
<h4 class="modal-title">{{'notify-message-modal.title' | translate}}</h4>
|
||||||
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
|
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex modal-body flex-column p-4">
|
<div class="container p-4">
|
||||||
<div *ngFor="let key of notifyMessageKeys">
|
<div *ngFor="let key of notifyMessageKeys">
|
||||||
<div class="d-flex w-100 justify-content-between mb-4">
|
<div class="row mb-4">
|
||||||
<div class="font-weight-bold mr-5">{{ key }}</div>
|
<div class="font-weight-bold col-sm">{{ key + '.notify-detail-modal' | translate}}</div>
|
||||||
<div class="text-nowrap text-truncate">{{ notifyMessage[key] }}</div>
|
<div class="text-nowrap col-sm text-right">{{ notifyMessage[key] }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -21,7 +21,6 @@
|
|||||||
[showViewModes]="false"
|
[showViewModes]="false"
|
||||||
[searchEnabled]="false"
|
[searchEnabled]="false"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
[useUniquePageId]="true"
|
|
||||||
></ds-themed-search>
|
></ds-themed-search>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,7 +16,7 @@ import { SearchConfigurationService } from "../../../../core/shared/search/searc
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminNotifyIncomingComponent {
|
export class AdminNotifyIncomingComponent {
|
||||||
protected readonly context = Context.CoarNotifyIncoming;
|
protected readonly context = Context.CoarNotify;
|
||||||
constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@
|
|||||||
[showViewModes]="false"
|
[showViewModes]="false"
|
||||||
[searchEnabled]="false"
|
[searchEnabled]="false"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
[useUniquePageId]="true"
|
|
||||||
></ds-themed-search>
|
></ds-themed-search>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,7 +16,7 @@ import { SearchConfigurationService } from "../../../../core/shared/search/searc
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminNotifyOutgoingComponent {
|
export class AdminNotifyOutgoingComponent {
|
||||||
protected readonly context = Context.CoarNotifyOutgoing;
|
protected readonly context = Context.CoarNotify;
|
||||||
|
|
||||||
constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
constructor(@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
<div class="table-responsive mt-2">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-nowrap">
|
||||||
|
<th scope="col">{{ 'notify-message-result.timestamp' | translate}}</th>
|
||||||
|
<th scope="col">{{'notify-message-result.repositoryItem' | translate}}</th>
|
||||||
|
<th scope="col">{{ 'notify-message-result.ldnService' | translate}}</th>
|
||||||
|
<th scope="col">{{ 'notify-message-result.type' | translate }}</th>
|
||||||
|
<th scope="col">{{ 'notify-message-result.status' | translate }}</th>
|
||||||
|
<th scope="col">{{ 'notify-message-result.action' | translate }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let message of (messagesSubject$ | async)">
|
||||||
|
<td>
|
||||||
|
<div>{{message.queueTimeout}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div *ngIf="isInbound">{{message.context}}</div>
|
||||||
|
<div *ngIf="!isInbound">{{message.object}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div *ngIf="isInbound">{{message.origin}}</div>
|
||||||
|
<div *ngIf="!isInbound">{{message.target}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{message.coarNotifyType}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{message.queueStatusLabel}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<button class="btn mb-2 btn-dark" (click)="openDetailModal(message)">{{ 'notify-message-result.detail' | translate }}</button>
|
||||||
|
<button *ngIf="message.queueStatusLabel === reprocessStatus" (click)="reprocessMessage(message)" class="btn btn-warning">
|
||||||
|
{{ 'notify-message-result.reprocess' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { AdminNotifySearchResultComponent } from './admin-notify-search-result.component';
|
||||||
|
|
||||||
|
describe('AdminNotifySearchResultComponent', () => {
|
||||||
|
let component: AdminNotifySearchResultComponent;
|
||||||
|
let fixture: ComponentFixture<AdminNotifySearchResultComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AdminNotifySearchResultComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminNotifySearchResultComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,122 @@
|
|||||||
|
import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { AdminNotifySearchResult } from '../models/admin-notify-message-search-result.model';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../core/shared/context.model';
|
||||||
|
import { AdminNotifyMessage, QueueStatusMap } from '../models/admin-notify-message.model';
|
||||||
|
import {
|
||||||
|
tabulatableObjectsComponent
|
||||||
|
} from '../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator';
|
||||||
|
import {
|
||||||
|
TabulatableResultListElementsComponent
|
||||||
|
} from '../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { AdminNotifyDetailModalComponent } from '../admin-notify-detail-modal/admin-notify-detail-modal.component';
|
||||||
|
import { LdnServicesService } from "../../admin-ldn-services/ldn-services-data/ldn-services-data.service";
|
||||||
|
import { BehaviorSubject, from, Observable, of, scan, Subscription, switchMap } from "rxjs";
|
||||||
|
import { combineLatest, filter, map, mergeMap, tap } from "rxjs/operators";
|
||||||
|
import { getAllSucceededRemoteDataPayload } from "../../../core/shared/operators";
|
||||||
|
import { ItemDataService } from "../../../core/data/item-data.service";
|
||||||
|
import { AdminNotifyMessagesService } from "../services/admin-notify-messages.service";
|
||||||
|
import { SearchConfigurationService } from "../../../core/shared/search/search-configuration.service";
|
||||||
|
import { SEARCH_CONFIG_SERVICE } from "../../../my-dspace-page/my-dspace-page.component";
|
||||||
|
|
||||||
|
@tabulatableObjectsComponent(PaginatedList<AdminNotifySearchResult>, ViewMode.Table, Context.CoarNotify)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-admin-notify-search-result',
|
||||||
|
templateUrl: './admin-notify-search-result.component.html',
|
||||||
|
styleUrls: ['./admin-notify-search-result.component.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
|
useClass: SearchConfigurationService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AdminNotifySearchResultComponent extends TabulatableResultListElementsComponent<PaginatedList<AdminNotifySearchResult>, AdminNotifySearchResult> implements OnInit, OnDestroy{
|
||||||
|
public messagesSubject$: BehaviorSubject<AdminNotifyMessage[]> = new BehaviorSubject([]);
|
||||||
|
public reprocessStatus = QueueStatusMap.QUEUE_STATUS_QUEUED_FOR_RETRY;
|
||||||
|
//we check on one type of config to render specific table headers
|
||||||
|
public isInbound: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(private modalService: NgbModal,
|
||||||
|
private adminNotifyMessagesService: AdminNotifyMessagesService,
|
||||||
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map messages on init for readable representation
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.mapDetailsToMessages()
|
||||||
|
this.subs.push(this.searchConfigService.getCurrentConfiguration('')
|
||||||
|
.subscribe(configuration => {
|
||||||
|
this.isInbound = configuration === 'NOTIFY.incoming';
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open modal for details visualization
|
||||||
|
* @param message the message to be displayed
|
||||||
|
*/
|
||||||
|
openDetailModal(message: AdminNotifyMessage) {
|
||||||
|
const modalRef = this.modalService.open(AdminNotifyDetailModalComponent);
|
||||||
|
const messageToOpen = {...message};
|
||||||
|
// we delete not necessary or not readable keys
|
||||||
|
if (this.isInbound) {
|
||||||
|
delete messageToOpen.target;
|
||||||
|
delete messageToOpen.object;
|
||||||
|
} else {
|
||||||
|
delete messageToOpen.context;
|
||||||
|
delete messageToOpen.origin;
|
||||||
|
}
|
||||||
|
delete messageToOpen._links;
|
||||||
|
delete messageToOpen.metadata;
|
||||||
|
delete messageToOpen.thumbnail;
|
||||||
|
delete messageToOpen.item;
|
||||||
|
delete messageToOpen.accessStatus;
|
||||||
|
delete messageToOpen.queueStatus;
|
||||||
|
|
||||||
|
const messageKeys = Object.keys(messageToOpen);
|
||||||
|
modalRef.componentInstance.notifyMessage = messageToOpen;
|
||||||
|
modalRef.componentInstance.notifyMessageKeys = messageKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reprocess message in status QUEUE_STATUS_QUEUED_FOR_RETRY and update results
|
||||||
|
* @param message the message to be reprocessed
|
||||||
|
*/
|
||||||
|
reprocessMessage(message: AdminNotifyMessage) {
|
||||||
|
this.subs.push(
|
||||||
|
this.adminNotifyMessagesService.reprocessMessage(message, this.messagesSubject$)
|
||||||
|
.subscribe(response => {
|
||||||
|
this.messagesSubject$.next(response)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map readable results to messages
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private mapDetailsToMessages() {
|
||||||
|
this.subs.push(this.adminNotifyMessagesService.getDetailedMessages(this.objects?.page.map(pageResult => pageResult.indexableObject))
|
||||||
|
.subscribe(response => {
|
||||||
|
this.messagesSubject$.next(response)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
@@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
<div class="table-responsive mt-2">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Timestamp</th>
|
|
||||||
<th scope="col">LDN Service</th>
|
|
||||||
<th scope="col">Repository Item</th>
|
|
||||||
<th scope="col">Type</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let message of (notifyMessages$ | async)">
|
|
||||||
<td>
|
|
||||||
<div>{{message.queueTimeout}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.origin}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.context}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.coarNotifyType}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.queueStatusLabel}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<button class="btn mb-2 btn-dark" (click)="openDetailModal(message)">Detail</button>
|
|
||||||
<button *ngIf="message.queueStatusLabel === reprocessStatus" (click)="reprocessMessage(message)" class="btn btn-warning">Reprocess</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
@@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { AdminNotifyIncomingSearchResultComponent } from './admin-notify-incoming-search-result.component';
|
|
||||||
|
|
||||||
describe('AdminNotifySearchResultComponent', () => {
|
|
||||||
let component: AdminNotifyIncomingSearchResultComponent;
|
|
||||||
let fixture: ComponentFixture<AdminNotifyIncomingSearchResultComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ AdminNotifyIncomingSearchResultComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AdminNotifyIncomingSearchResultComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,97 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { AdminNotifySearchResult } from '../../models/admin-notify-message-search-result.model';
|
|
||||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
|
||||||
import { Context } from '../../../../core/shared/context.model';
|
|
||||||
import { AdminNotifyMessage, QueueStatusMap } from '../../models/admin-notify-message.model';
|
|
||||||
import {
|
|
||||||
tabulatableObjectsComponent
|
|
||||||
} from '../../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator';
|
|
||||||
import {
|
|
||||||
TabulatableResultListElementsComponent
|
|
||||||
} from '../../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component';
|
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { AdminNotifyDetailModalComponent } from '../../admin-notify-detail-modal/admin-notify-detail-modal.component';
|
|
||||||
import { LdnServicesService } from "../../../admin-ldn-services/ldn-services-data/ldn-services-data.service";
|
|
||||||
import { BehaviorSubject, concatMap, from, Observable, of, scan, switchMap } from "rxjs";
|
|
||||||
import { RemoteData } from "../../../../core/data/remote-data";
|
|
||||||
import { LdnService } from "../../../admin-ldn-services/ldn-services-model/ldn-services.model";
|
|
||||||
import { filter, map, mergeMap, take, tap, toArray } from "rxjs/operators";
|
|
||||||
import { getAllSucceededRemoteDataPayload } from "../../../../core/shared/operators";
|
|
||||||
import { ItemDataService } from "../../../../core/data/item-data.service";
|
|
||||||
import { AdminNotifyMessagesService } from "../../services/admin-notify-messages.service";
|
|
||||||
|
|
||||||
@tabulatableObjectsComponent(PaginatedList<AdminNotifySearchResult>, ViewMode.Table, Context.CoarNotifyIncoming)
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-admin-notify-search-result',
|
|
||||||
templateUrl: './admin-notify-incoming-search-result.component.html',
|
|
||||||
styleUrls: ['./admin-notify-incoming-search-result.component.scss']
|
|
||||||
})
|
|
||||||
export class AdminNotifyIncomingSearchResultComponent extends TabulatableResultListElementsComponent<PaginatedList<AdminNotifySearchResult>, AdminNotifySearchResult> implements OnInit{
|
|
||||||
public notifyMessages: AdminNotifyMessage[];
|
|
||||||
public notifyMessages$: Observable<AdminNotifyMessage[]>;
|
|
||||||
public reprocessStatus = QueueStatusMap.QUEUE_STATUS_QUEUED_FOR_RETRY;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(private modalService: NgbModal,
|
|
||||||
private ldnServicesService: LdnServicesService,
|
|
||||||
private itemDataService: ItemDataService,
|
|
||||||
private adminNotifyMessagesService: AdminNotifyMessagesService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map messages on init for readable representation
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
|
||||||
this.notifyMessages = this.objects?.page.map(object => {
|
|
||||||
const indexableObject = object.indexableObject;
|
|
||||||
indexableObject.coarNotifyType = indexableObject.coarNotifyType.split(':')[1];
|
|
||||||
indexableObject.queueStatusLabel = QueueStatusMap[indexableObject.queueStatusLabel];
|
|
||||||
return indexableObject;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.notifyMessages$ = from(this.notifyMessages).pipe(
|
|
||||||
mergeMap(message => of(message)),
|
|
||||||
mergeMap(message =>
|
|
||||||
message.origin ? this.ldnServicesService.findById(message.origin.toString()).pipe(
|
|
||||||
getAllSucceededRemoteDataPayload(),
|
|
||||||
map(detail => ({...message, origin: detail.name}))
|
|
||||||
) : of(message),
|
|
||||||
),
|
|
||||||
mergeMap(message =>
|
|
||||||
message.context ? this.itemDataService.findById(message.context.toString()).pipe(
|
|
||||||
getAllSucceededRemoteDataPayload(),
|
|
||||||
map(detail => ({...message, context: detail.name}))
|
|
||||||
) : of(message),
|
|
||||||
),
|
|
||||||
scan((acc: any, value: any) => [...acc, value], []),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open modal for details visualization
|
|
||||||
* @param message the message to be displayed
|
|
||||||
*/
|
|
||||||
openDetailModal(message: AdminNotifyMessage) {
|
|
||||||
const modalRef = this.modalService.open(AdminNotifyDetailModalComponent);
|
|
||||||
const messageKeys = Object.keys(message);
|
|
||||||
const keysToRead = [];
|
|
||||||
messageKeys.forEach((key) => {
|
|
||||||
if (typeof message[key] !== 'object') {
|
|
||||||
keysToRead.push(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
modalRef.componentInstance.notifyMessage = message;
|
|
||||||
modalRef.componentInstance.notifyMessageKeys = keysToRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reprocess message in status QUEUE_STATUS_QUEUED_FOR_RETRY and update results
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
reprocessMessage(message: AdminNotifyMessage) {
|
|
||||||
// TODO implement reprocess
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
<div class="table-responsive mt-2">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Timestamp</th>
|
|
||||||
<th scope="col">Repository Item</th>
|
|
||||||
<th scope="col">LDN Service</th>
|
|
||||||
<th scope="col">Type</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let message of (notifyMessages$ | async)">
|
|
||||||
<td>
|
|
||||||
<div>{{message.queueTimeout}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.object}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.target}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.coarNotifyType}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div>{{message.queueStatusLabel}}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<button class="btn mb-2 btn-dark" (click)="openDetailModal(message)">Detail</button>
|
|
||||||
<button *ngIf="message.queueStatusLabel === reprocessStatus" (click)="reprocessMessage(message)" class="btn btn-warning">Reprocess</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
@@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { AdminNotifyOutgoingSearchResultComponent } from './admin-notify-outgoing-search-result.component';
|
|
||||||
|
|
||||||
describe('AdminNotifySearchResultComponent', () => {
|
|
||||||
let component: AdminNotifyOutgoingSearchResultComponent;
|
|
||||||
let fixture: ComponentFixture<AdminNotifyOutgoingSearchResultComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ AdminNotifyOutgoingSearchResultComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AdminNotifyOutgoingSearchResultComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,98 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { AdminNotifySearchResult } from '../../models/admin-notify-message-search-result.model';
|
|
||||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
|
||||||
import { Context } from '../../../../core/shared/context.model';
|
|
||||||
import { AdminNotifyMessage, QueueStatusMap } from '../../models/admin-notify-message.model';
|
|
||||||
import {
|
|
||||||
tabulatableObjectsComponent
|
|
||||||
} from '../../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator';
|
|
||||||
import {
|
|
||||||
TabulatableResultListElementsComponent
|
|
||||||
} from '../../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component';
|
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { AdminNotifyDetailModalComponent } from '../../admin-notify-detail-modal/admin-notify-detail-modal.component';
|
|
||||||
import { LdnServicesService } from "../../../admin-ldn-services/ldn-services-data/ldn-services-data.service";
|
|
||||||
import { from, Observable, of, scan, switchMap } from "rxjs";
|
|
||||||
import { combineLatest, filter, map, mergeMap } from "rxjs/operators";
|
|
||||||
import { getAllSucceededRemoteDataPayload } from "../../../../core/shared/operators";
|
|
||||||
import { ItemDataService } from "../../../../core/data/item-data.service";
|
|
||||||
import { AdminNotifyMessagesService } from "../../services/admin-notify-messages.service";
|
|
||||||
|
|
||||||
@tabulatableObjectsComponent(PaginatedList<AdminNotifySearchResult>, ViewMode.Table, Context.CoarNotifyOutgoing)
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-admin-notify-search-result',
|
|
||||||
templateUrl: './admin-notify-outgoing-search-result.component.html',
|
|
||||||
styleUrls: ['./admin-notify-outgoing-search-result.component.scss']
|
|
||||||
})
|
|
||||||
export class AdminNotifyOutgoingSearchResultComponent extends TabulatableResultListElementsComponent<PaginatedList<AdminNotifySearchResult>, AdminNotifySearchResult> implements OnInit{
|
|
||||||
public notifyMessages: AdminNotifyMessage[];
|
|
||||||
public notifyMessages$: Observable<AdminNotifyMessage[]>;
|
|
||||||
public reprocessStatus = QueueStatusMap.QUEUE_STATUS_QUEUED_FOR_RETRY;
|
|
||||||
|
|
||||||
constructor(private modalService: NgbModal,
|
|
||||||
private ldnServicesService: LdnServicesService,
|
|
||||||
private itemDataService: ItemDataService,
|
|
||||||
private adminNotifyMessagesService: AdminNotifyMessagesService) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map messages on init for readable representation
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
|
||||||
this.mapDetailsToMessages()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open modal for details visualization
|
|
||||||
* @param message the message to be displayed
|
|
||||||
*/
|
|
||||||
openDetailModal(message: AdminNotifyMessage) {
|
|
||||||
const modalRef = this.modalService.open(AdminNotifyDetailModalComponent);
|
|
||||||
const messageKeys = Object.keys(message);
|
|
||||||
modalRef.componentInstance.notifyMessage = message;
|
|
||||||
modalRef.componentInstance.notifyMessageKeys = messageKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reprocess message in status QUEUE_STATUS_QUEUED_FOR_RETRY and update results
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
reprocessMessage(message: AdminNotifyMessage) {
|
|
||||||
this.adminNotifyMessagesService.findById(message.id).pipe(getAllSucceededRemoteDataPayload()).subscribe(response => {
|
|
||||||
console.log(response);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map readable results to messages
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private mapDetailsToMessages() {
|
|
||||||
this.notifyMessages = this.objects?.page.map(object => {
|
|
||||||
const indexableObject = object.indexableObject;
|
|
||||||
indexableObject.coarNotifyType = indexableObject.coarNotifyType.split(':')[1];
|
|
||||||
indexableObject.queueStatusLabel = QueueStatusMap[indexableObject.queueStatusLabel];
|
|
||||||
return indexableObject;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.notifyMessages$ = from(this.notifyMessages).pipe(
|
|
||||||
mergeMap(message => of(message)),
|
|
||||||
mergeMap(message =>
|
|
||||||
message.target ? this.ldnServicesService.findById(message.target.toString()).pipe(
|
|
||||||
getAllSucceededRemoteDataPayload(),
|
|
||||||
map(detail => ({...message, target: detail.name}))
|
|
||||||
) : of(message),
|
|
||||||
),
|
|
||||||
mergeMap(message =>
|
|
||||||
message.object ? this.itemDataService.findById(message.object.toString()).pipe(
|
|
||||||
getAllSucceededRemoteDataPayload(),
|
|
||||||
map(detail => ({...message, object: detail.name}))
|
|
||||||
) : of(message),
|
|
||||||
),
|
|
||||||
scan((acc: any, value: any) => [...acc, value], []),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,11 @@
|
|||||||
import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
|
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
|
||||||
import { typedObject } from '../../../core/cache/builders/build-decorators';
|
import { typedObject } from '../../../core/cache/builders/build-decorators';
|
||||||
import { ADMIN_NOTIFY_MESSAGE } from './admin-notify-message.resource-type';
|
import { ADMIN_NOTIFY_MESSAGE } from './admin-notify-message.resource-type';
|
||||||
import { excludeFromEquals } from '../../../core/utilities/equals.decorators';
|
import { excludeFromEquals } from '../../../core/utilities/equals.decorators';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
export enum QueueStatusMap {
|
export enum QueueStatusMap {
|
||||||
QUEUE_STATUS_PROCESSED = 'Processed',
|
QUEUE_STATUS_PROCESSED = 'Processed',
|
||||||
@@ -107,6 +108,25 @@ export class AdminNotifyMessage extends DSpaceObject {
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
queueStatus: number;
|
queueStatus: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thumbnail link used when browsing items with showThumbs config enabled.
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
thumbnail: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observable pointing to the item itself
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
item: Observable<AdminNotifyMessage>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observable pointing to the access status of the item
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
accessStatus: Observable<AdminNotifyMessage>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@deserialize
|
@deserialize
|
||||||
_links: {
|
_links: {
|
||||||
|
@@ -10,7 +10,7 @@ import {HALEndpointService} from '../../../core/shared/hal-endpoint.service';
|
|||||||
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
import {FindListOptions} from '../../../core/data/find-list-options.model';
|
import {FindListOptions} from '../../../core/data/find-list-options.model';
|
||||||
import {FollowLinkConfig} from '../../../shared/utils/follow-link-config.model';
|
import {FollowLinkConfig} from '../../../shared/utils/follow-link-config.model';
|
||||||
import {Observable} from 'rxjs';
|
import { BehaviorSubject, from, Observable, of, scan } from 'rxjs';
|
||||||
import {RemoteData} from '../../../core/data/remote-data';
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
import {PaginatedList} from '../../../core/data/paginated-list.model';
|
import {PaginatedList} from '../../../core/data/paginated-list.model';
|
||||||
import {NoContent} from '../../../core/shared/NoContent.model';
|
import {NoContent} from '../../../core/shared/NoContent.model';
|
||||||
@@ -21,7 +21,13 @@ import {RestRequestMethod} from '../../../core/data/rest-request-method';
|
|||||||
import {CreateData, CreateDataImpl} from '../../../core/data/base/create-data';
|
import {CreateData, CreateDataImpl} from '../../../core/data/base/create-data';
|
||||||
import {SearchDataImpl} from '../../../core/data/base/search-data';
|
import {SearchDataImpl} from '../../../core/data/base/search-data';
|
||||||
import { ADMIN_NOTIFY_MESSAGE } from "../models/admin-notify-message.resource-type";
|
import { ADMIN_NOTIFY_MESSAGE } from "../models/admin-notify-message.resource-type";
|
||||||
import { AdminNotifyMessage } from "../models/admin-notify-message.model";
|
import { AdminNotifyMessage, QueueStatusMap } from "../models/admin-notify-message.model";
|
||||||
|
import { SearchResult } from "../../../shared/search/models/search-result.model";
|
||||||
|
import { map, mergeMap } from "rxjs/operators";
|
||||||
|
import { getAllSucceededRemoteDataPayload } from "../../../core/shared/operators";
|
||||||
|
import { AdminNotifySearchResult } from "../models/admin-notify-message-search-result.model";
|
||||||
|
import { LdnServicesService } from "../../admin-ldn-services/ldn-services-data/ldn-services-data.service";
|
||||||
|
import { ItemDataService } from "../../../core/data/item-data.service";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injectable service responsible for fetching/sending data from/to the REST API on the messages endpoint.
|
* Injectable service responsible for fetching/sending data from/to the REST API on the messages endpoint.
|
||||||
@@ -39,7 +45,77 @@ export class AdminNotifyMessagesService extends IdentifiableDataService<AdminNot
|
|||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
|
private ldnServicesService: LdnServicesService,
|
||||||
|
private itemDataService: ItemDataService,
|
||||||
) {
|
) {
|
||||||
super('messages', requestService, rdbService, objectCache, halService);
|
super('messages', requestService, rdbService, objectCache, halService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map labels to readable info for user
|
||||||
|
* @param message the message to which map the labels
|
||||||
|
*/
|
||||||
|
public formatMessageLabels(message: AdminNotifyMessage) : AdminNotifyMessage {
|
||||||
|
message.coarNotifyType = message.coarNotifyType?.split(':')[1];
|
||||||
|
message.queueStatusLabel = QueueStatusMap[message.queueStatusLabel];
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add detailed information to each message
|
||||||
|
* @param messages the messages to which add detailded info
|
||||||
|
*/
|
||||||
|
public getDetailedMessages(messages: AdminNotifyMessage[]) : Observable<AdminNotifyMessage[]> {
|
||||||
|
return from(messages.map(message => this.formatMessageLabels(message))).pipe(
|
||||||
|
mergeMap(message =>
|
||||||
|
message.target ? this.ldnServicesService.findById(message.target.toString()).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
map(detail => ({...message, target: detail.name}))
|
||||||
|
) : of(message),
|
||||||
|
),
|
||||||
|
mergeMap(message =>
|
||||||
|
message.object ? this.itemDataService.findById(message.object.toString()).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
map(detail => ({...message, object: detail.name}))
|
||||||
|
) : of(message),
|
||||||
|
),
|
||||||
|
mergeMap(message =>
|
||||||
|
message.origin ? this.ldnServicesService.findById(message.origin.toString()).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
map(detail => ({...message, origin: detail.name}))
|
||||||
|
) : of(message),
|
||||||
|
),
|
||||||
|
mergeMap(message =>
|
||||||
|
message.context ? this.itemDataService.findById(message.context.toString()).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
map(detail => ({...message, context: detail.name}))
|
||||||
|
) : of(message),
|
||||||
|
),
|
||||||
|
scan((acc: any, value: any) => [...acc, value], []),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reprocess message in status QUEUE_STATUS_QUEUED_FOR_RETRY and update results
|
||||||
|
* @param message the message to reprocess
|
||||||
|
* @param messageSubject the current visualised messages source
|
||||||
|
*/
|
||||||
|
public reprocessMessage(message: AdminNotifyMessage, messageSubject: BehaviorSubject<AdminNotifyMessage[]>) : Observable<AdminNotifyMessage[]> {
|
||||||
|
return this.findById(message.id).pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
map(reprocessedMessage => this.formatMessageLabels(reprocessedMessage)),
|
||||||
|
mergeMap((newMessage) => messageSubject.pipe(
|
||||||
|
map(messages => {
|
||||||
|
const messageToUpdate = messages.find(currentMessage => currentMessage.id === message.id);
|
||||||
|
const indexOfMessageToUpdate = messages.indexOf(messageToUpdate);
|
||||||
|
newMessage.target = messageToUpdate.target;
|
||||||
|
newMessage.object = messageToUpdate.object;
|
||||||
|
newMessage.origin = messageToUpdate.origin;
|
||||||
|
newMessage.context = messageToUpdate.context;
|
||||||
|
messages[indexOfMessageToUpdate] = newMessage;
|
||||||
|
return messages
|
||||||
|
})
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,6 +40,5 @@ export enum Context {
|
|||||||
|
|
||||||
Bitstream = 'bitstream',
|
Bitstream = 'bitstream',
|
||||||
|
|
||||||
CoarNotifyIncoming = 'coarNotifyIncoming',
|
CoarNotify = 'coarNotify',
|
||||||
CoarNotifyOutgoing = 'coarNotifyOutgoing',
|
|
||||||
}
|
}
|
||||||
|
@@ -3529,7 +3529,51 @@
|
|||||||
|
|
||||||
"sorting.queue_attempts.ASC": "Queue attempted Ascending",
|
"sorting.queue_attempts.ASC": "Queue attempted Ascending",
|
||||||
|
|
||||||
"orgunit.listelement.badge": "Organizational Unit",
|
"type.notify-detail-modal": "Type",
|
||||||
|
|
||||||
|
"id.notify-detail-modal": "Id",
|
||||||
|
|
||||||
|
"coarNotifyType.notify-detail-modal": "Coar Notify type",
|
||||||
|
|
||||||
|
"activityStreamType.notify-detail-modal": "Activity stream type",
|
||||||
|
|
||||||
|
"inReplyTo.notify-detail-modal": "In reply to",
|
||||||
|
|
||||||
|
"object.notify-detail-modal": "Repository Item",
|
||||||
|
|
||||||
|
"context.notify-detail-modal": "Repository Item",
|
||||||
|
|
||||||
|
"queueAttempts.notify-detail-modal": "Queue attempts",
|
||||||
|
|
||||||
|
"queueLastStartTime.notify-detail-modal": "Queue last started",
|
||||||
|
|
||||||
|
"origin.notify-detail-modal": "LDN Service",
|
||||||
|
|
||||||
|
"target.notify-detail-modal": "LDN Service",
|
||||||
|
|
||||||
|
"queueStatusLabel.notify-detail-modal": "Queue status",
|
||||||
|
|
||||||
|
"queueTimeout.notify-detail-modal": "Queue timeout",
|
||||||
|
|
||||||
|
"notify-message-modal.title": "Message Detail",
|
||||||
|
|
||||||
|
"notify-message-result.timestamp": "Timestamp",
|
||||||
|
|
||||||
|
"notify-message-result.repositoryItem": "Repository Item",
|
||||||
|
|
||||||
|
"notify-message-result.ldnService": "LDN Service",
|
||||||
|
|
||||||
|
"notify-message-result.type": "Type",
|
||||||
|
|
||||||
|
"notify-message-result.status": "Status",
|
||||||
|
|
||||||
|
"notify-message-result.action": "Action",
|
||||||
|
|
||||||
|
"notify-message-result.detail": "Detail",
|
||||||
|
|
||||||
|
"notify-message-result.reprocess": "Reprocess",
|
||||||
|
|
||||||
|
"orgunit.listelement.badge": "Repository Item",
|
||||||
|
|
||||||
"orgunit.listelement.no-title": "Untitled",
|
"orgunit.listelement.no-title": "Untitled",
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user