mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
71712: Export metadata CSV; WIP
This commit is contained in:
@@ -2,8 +2,13 @@ import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { combineLatest as combineLatestObservable } from 'rxjs';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
import { first, map, take } from 'rxjs/operators';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { ProcessDataService } from '../../core/data/processes/process-data.service';
|
||||
import { ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Script } from '../../process-page/scripts/script.model';
|
||||
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
||||
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
||||
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
||||
@@ -11,6 +16,10 @@ import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/mod
|
||||
import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
||||
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
||||
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
||||
import {
|
||||
ExportMetadataSelectorComponent,
|
||||
METADATA_EXPORT_SCRIPT_NAME
|
||||
} from '../../shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
|
||||
import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
|
||||
@@ -64,7 +73,8 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
private variableService: CSSVariableService,
|
||||
private authService: AuthService,
|
||||
private modalService: NgbModal,
|
||||
private authorizationService: AuthorizationDataService
|
||||
private authorizationService: AuthorizationDataService,
|
||||
private scriptDataService: ScriptDataService,
|
||||
) {
|
||||
super(menuService, injector);
|
||||
}
|
||||
@@ -75,6 +85,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.createMenu();
|
||||
this.createSiteAdministratorMenuSections();
|
||||
this.createExportMenuSections();
|
||||
super.ngOnInit();
|
||||
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
|
||||
this.authService.isAuthenticated()
|
||||
@@ -259,61 +270,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* Export */
|
||||
{
|
||||
id: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'sign-out-alt',
|
||||
index: 3
|
||||
},
|
||||
{
|
||||
id: 'export_community',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_community',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'export_collection',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_collection',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'export_item',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_item',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
}, {
|
||||
id: 'export_metadata',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_metadata',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
|
||||
/* Statistics */
|
||||
{
|
||||
@@ -362,6 +318,85 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the export scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createExportMenuSections() {
|
||||
const isAuthorized$: Observable<boolean> = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
|
||||
isAuthorized$.subscribe((authorized: boolean) => {
|
||||
if (authorized) {
|
||||
const metadataExportScriptExists$ = this.scriptDataService.scripWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME);
|
||||
metadataExportScriptExists$.subscribe((metadataExportScriptExists: boolean) => {
|
||||
const menuList = [
|
||||
/* Export */
|
||||
{
|
||||
id: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'sign-out-alt',
|
||||
index: 3
|
||||
},
|
||||
{
|
||||
id: 'export_community',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_community',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'export_collection',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_collection',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'export_item',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.export_item',
|
||||
link: ''
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'export_metadata',
|
||||
parentID: 'export',
|
||||
active: true,
|
||||
visible: authorized && metadataExportScriptExists,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_metadata',
|
||||
function: () => {
|
||||
this.modalService.open(ExportMetadataSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true
|
||||
})));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
|
@@ -21,7 +21,6 @@ import { HostWindowState } from './shared/search/host-window.reducer';
|
||||
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
||||
import { isAuthenticated } from './core/auth/selectors';
|
||||
import { AuthService } from './core/auth/auth.service';
|
||||
import variables from '../styles/_exposed_variables.scss';
|
||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { MenuID } from './shared/menu/initial-menus-state';
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../shared/operators';
|
||||
import { DataService } from '../data.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
@@ -12,6 +13,7 @@ import { Script } from '../../../process-page/scripts/script.model';
|
||||
import { ProcessParameter } from '../../../process-page/processes/process-parameter.model';
|
||||
import { find, map, switchMap } from 'rxjs/operators';
|
||||
import { URLCombiner } from '../../url-combiner/url-combiner';
|
||||
import { PaginatedList } from '../paginated-list';
|
||||
import { MultipartPostRequest, RestRequest } from '../request.models';
|
||||
import { RequestService } from '../request.service';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -58,4 +60,23 @@ export class ScriptDataService extends DataService<Script> {
|
||||
});
|
||||
return form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a script with given name exist; user needs to be allowed to execute script for this to to not throw a 401 Unauthorized
|
||||
* @param scriptName script we want to check exists (and we can execute)
|
||||
*/
|
||||
public scripWithNameExistsAndCanExecute(scriptName: string): Observable<boolean> {
|
||||
return this.findAll().pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((scriptsRD: PaginatedList<Script>) => {
|
||||
let found = false;
|
||||
scriptsRD.page.forEach((script: Script) => {
|
||||
if (script.id === scriptName) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@@ -4,4 +4,5 @@ export enum DSpaceObjectType {
|
||||
ITEM = 'ITEM',
|
||||
COLLECTION = 'COLLECTION',
|
||||
COMMUNITY = 'COMMUNITY',
|
||||
DSPACEOBJECT = 'DSPACEOBJECT',
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ export class DSOSelectorComponent implements OnInit {
|
||||
return this.searchService.search(
|
||||
new PaginatedSearchOptions({
|
||||
query: query,
|
||||
dsoType: this.type,
|
||||
dsoType: this.type != DSpaceObjectType.DSPACEOBJECT ? this.type : null,
|
||||
pagination: this.defaultPagination
|
||||
})
|
||||
)
|
||||
|
@@ -1,16 +1,15 @@
|
||||
import { Injectable, Input, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
|
||||
export enum SelectorActionType {
|
||||
CREATE = 'create',
|
||||
EDIT = 'edit'
|
||||
EDIT = 'edit',
|
||||
EXPORT_METADATA = 'export-metadata'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +24,7 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit {
|
||||
@Input() dsoRD: RemoteData<DSpaceObject>;
|
||||
|
||||
/**
|
||||
* The type of the DSO that's being edited or created
|
||||
* The type of the DSO that's being edited, created or exported
|
||||
*/
|
||||
objectType: DSpaceObjectType;
|
||||
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EditItemSelectorComponent } from './edit-item-selector.component';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { RouterStub } from '../../../testing/router.stub';
|
||||
import * as itemRouter from '../../../../+item-page/item-page-routing.module';
|
||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||
|
@@ -0,0 +1,79 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||
import { RequestEntry } from '../../../../core/data/request.reducer';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model';
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { NotificationsService } from '../../../notifications/notifications.service';
|
||||
import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component';
|
||||
|
||||
export const METADATA_EXPORT_SCRIPT_NAME: string = 'metadata-export';
|
||||
|
||||
/**
|
||||
* Component to wrap a list of existing dso's inside a modal
|
||||
* Used to choose a dso from to export metadata of
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-edit-item-selector',
|
||||
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||
})
|
||||
export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||
objectType = DSpaceObjectType.DSPACEOBJECT;
|
||||
selectorType = DSpaceObjectType.DSPACEOBJECT;
|
||||
action = SelectorActionType.EXPORT_METADATA;
|
||||
|
||||
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router,
|
||||
protected notificationsService: NotificationsService, protected translationService: TranslateService,
|
||||
protected scriptDataService: ScriptDataService) {
|
||||
super(activeModal, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the dso is a collection or community: start export-metadata script & navigate to process if successful
|
||||
* Otherwise show error message
|
||||
*/
|
||||
navigate(dso: DSpaceObject) {
|
||||
if (dso instanceof Collection || dso instanceof Community) {
|
||||
this.startScriptNotifyAndRedirect(dso, dso.handle);
|
||||
} else {
|
||||
this.notificationsService.error(this.translationService.get('dso-selector.export-metadata.notValidDSO'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start export-metadata script of dso & navigate to process if successful
|
||||
* Otherwise show error message
|
||||
* @param dso Dso to export
|
||||
*/
|
||||
private startScriptNotifyAndRedirect(dso: DSpaceObject, handle: string) {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '-i', value: handle }),
|
||||
Object.assign(new ProcessParameter(), { name: '-f', value: dso.uuid + '.csv' }),
|
||||
];
|
||||
this.scriptDataService.invoke(METADATA_EXPORT_SCRIPT_NAME, parameterValues, [])
|
||||
.pipe(take(1))
|
||||
.subscribe((requestEntry: RequestEntry) => {
|
||||
if (requestEntry.response.isSuccessful) {
|
||||
const title = this.translationService.get('process.new.notification.success.title');
|
||||
const content = this.translationService.get('process.new.notification.success.content');
|
||||
this.notificationsService.success(title, content);
|
||||
const response: any = requestEntry.response;
|
||||
if (isNotEmpty(response.resourceSelfLinks)) {
|
||||
const processNumber = response.resourceSelfLinks[0].split('/').pop();
|
||||
this.router.navigateByUrl('/processes/' + processNumber);
|
||||
}
|
||||
} else {
|
||||
const title = this.translationService.get('process.new.notification.error.title');
|
||||
const content = this.translationService.get('process.new.notification.error.content');
|
||||
this.notificationsService.error(title, content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'
|
||||
|
||||
import { NgxPaginationModule } from 'ngx-pagination';
|
||||
import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role/comcol-role.component';
|
||||
import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component';
|
||||
|
||||
import { FileUploadModule } from 'ng2-file-upload';
|
||||
@@ -397,7 +398,8 @@ const COMPONENTS = [
|
||||
EpersonSearchBoxComponent,
|
||||
GroupSearchBoxComponent,
|
||||
FileDownloadLinkComponent,
|
||||
CollectionDropdownComponent
|
||||
CollectionDropdownComponent,
|
||||
ExportMetadataSelectorComponent
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -474,6 +476,7 @@ const ENTRY_COMPONENTS = [
|
||||
ClaimedTaskActionsEditMetadataComponent,
|
||||
FileDownloadLinkComponent,
|
||||
CurationFormComponent,
|
||||
ExportMetadataSelectorComponent
|
||||
];
|
||||
|
||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||
|
@@ -965,6 +965,10 @@
|
||||
|
||||
"dso-selector.edit.item.head": "Edit item",
|
||||
|
||||
"dso-selector.export-metadata.dspaceobject.head": "Export metadata from",
|
||||
|
||||
"dso-selector.export-metadata.notValidDSO": "You can only export metadata for collections and communities",
|
||||
|
||||
"dso-selector.no-results": "No {{ type }} found",
|
||||
|
||||
"dso-selector.placeholder": "Search for a {{ type }}",
|
||||
|
Reference in New Issue
Block a user