From c1498424f3605eb61cb90cc7d853ebf86305ee8f Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Tue, 13 Sep 2022 16:18:32 +0530 Subject: [PATCH] CST-6685 changes for import batch --- .../batch-import-page.component.html | 35 ++++ .../batch-import-page.component.spec.ts | 149 ++++++++++++++++++ .../batch-import-page.component.ts | 123 +++++++++++++++ src/app/admin/admin-routing.module.ts | 7 + src/app/admin/admin.module.ts | 4 +- .../data/processes/script-data.service.ts | 1 + src/app/menu.resolver.spec.ts | 3 + src/app/menu.resolver.ts | 27 ++-- .../dso-selector-modal-wrapper.component.ts | 1 + .../import-batch-selector.component.spec.ts | 100 ++++++++++++ .../import-batch-selector.component.ts | 45 ++++++ src/app/shared/shared.module.ts | 5 + src/assets/i18n/en.json5 | 27 +++- 13 files changed, 508 insertions(+), 19 deletions(-) create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.html create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.html b/src/app/admin/admin-import-batch-page/batch-import-page.component.html new file mode 100644 index 0000000000..dbc8c74437 --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.html @@ -0,0 +1,35 @@ +
+ +

{{'admin.batch-import.page.help' | translate}}

+

+ selected collection: {{getDspaceObjectName()}}  + {{'admin.batch-import.page.remove' | translate}} +

+

+ +

+
+
+ + +
+ + {{'admin.batch-import.page.validateOnly.hint' | translate}} + +
+ + + + +
+ + +
+
diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts new file mode 100644 index 0000000000..eecf05f543 --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts @@ -0,0 +1,149 @@ +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; +import { BatchImportPageComponent } from './batch-import-page.component'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive'; +import { FileValidator } from '../../shared/utils/require-file.validator'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { + BATCH_IMPORT_SCRIPT_NAME, + ScriptDataService +} from '../../core/data/processes/script-data.service'; +import { Router } from '@angular/router'; +import { Location } from '@angular/common'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; + +describe('BatchImportPageComponent', () => { + let component: BatchImportPageComponent; + let fixture: ComponentFixture; + + let notificationService: NotificationsServiceStub; + let scriptService: any; + let router; + let locationStub; + + function init() { + notificationService = new NotificationsServiceStub(); + scriptService = jasmine.createSpyObj('scriptService', + { + invoke: createSuccessfulRemoteDataObject$({ processId: '46' }) + } + ); + router = jasmine.createSpyObj('router', { + navigateByUrl: jasmine.createSpy('navigateByUrl') + }); + locationStub = jasmine.createSpyObj('location', { + back: jasmine.createSpy('back') + }); + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + FormsModule, + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [BatchImportPageComponent, FileValueAccessorDirective, FileValidator], + providers: [ + { provide: NotificationsService, useValue: notificationService }, + { provide: ScriptDataService, useValue: scriptService }, + { provide: Router, useValue: router }, + { provide: Location, useValue: locationStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BatchImportPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if back button is pressed', () => { + beforeEach(fakeAsync(() => { + const proceed = fixture.debugElement.query(By.css('#backButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('should do location.back', () => { + expect(locationStub.back).toHaveBeenCalled(); + }); + }); + + describe('if file is set', () => { + let fileMock: File; + + beforeEach(() => { + fileMock = new File([''], 'filename.zip', { type: 'application/zip' }); + component.setFile(fileMock); + }); + + describe('if proceed button is pressed without validate only', () => { + beforeEach(fakeAsync(() => { + component.validateOnly = false; + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('metadata-import script is invoked with --zip fileName and the mockFile', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); + }); + it('success notification is shown', () => { + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46'); + }); + }); + + describe('if proceed button is pressed with validate only', () => { + beforeEach(fakeAsync(() => { + component.validateOnly = true; + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), + Object.assign(new ProcessParameter(), { name: '-v', value: true }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); + }); + it('success notification is shown', () => { + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46'); + }); + }); + + describe('if proceed is pressed; but script invoke fails', () => { + beforeEach(fakeAsync(() => { + jasmine.getEnv().allowRespy(true); + spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('error notification is shown', () => { + expect(notificationService.error).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts new file mode 100644 index 0000000000..2985aff3aa --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -0,0 +1,123 @@ +import { Component } from '@angular/core'; +import { Location } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { BATCH_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service'; +import { Router } from '@angular/router'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { Process } from '../../process-page/processes/process.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths'; +import { + ImportBatchSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { take } from 'rxjs/operators'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; + +@Component({ + selector: 'ds-batch-import-page', + templateUrl: './batch-import-page.component.html' +}) +export class BatchImportPageComponent { + /** + * The current value of the file + */ + fileObject: File; + + /** + * The validate only flag + */ + validateOnly = true; + /** + * dso object for community or collection + */ + dso: DSpaceObject = null; + + public constructor(private location: Location, + protected translate: TranslateService, + protected notificationsService: NotificationsService, + private scriptDataService: ScriptDataService, + private router: Router, + private modalService: NgbModal, + private dsoNameService: DSONameService) { + } + + /** + * Set file + * @param file + */ + setFile(file) { + this.fileObject = file; + } + + /** + * When return button is pressed go to previous location + */ + public onReturn() { + this.location.back(); + } + + public selectCollection() { + const modalRef = this.modalService.open(ImportBatchSelectorComponent); + modalRef.componentInstance.response.pipe(take(1)).subscribe((dso) => { + this.dso = dso || null; + }); + } + + /** + * Starts import-metadata script with --zip fileName (and the selected file) + */ + public importMetadata() { + if (this.fileObject == null) { + this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile')); + } else { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }), + ]; + if (this.dso) { + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid })); + } + if (this.validateOnly) { + parameterValues.push(Object.assign(new ProcessParameter(), { name: '-v', value: true })); + } + + this.scriptDataService.invoke(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]).pipe( + getFirstCompletedRemoteData(), + ).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translate.get('process.new.notification.success.title'); + const content = this.translate.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + } else { + const title = this.translate.get('process.new.notification.error.title'); + const content = this.translate.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + } + }); + } + } + + /** + * return selected dspace object name + */ + getDspaceObjectName(): string { + if (this.dso) { + return this.dsoNameService.getName(this.dso); + } + return null; + } + + /** + * remove selected dso object + */ + removeDspaceObject(): void { + this.dso = null; + } +} diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index ee5cb8737b..1ea20bc9a0 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -7,6 +7,7 @@ import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; import { REGISTRIES_MODULE_PATH } from './admin-routing-paths'; +import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; @NgModule({ imports: [ @@ -40,6 +41,12 @@ import { REGISTRIES_MODULE_PATH } from './admin-routing-paths'; component: MetadataImportPageComponent, data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' } }, + { + path: 'batch-import', + resolve: { breadcrumb: I18nBreadcrumbResolver }, + component: BatchImportPageComponent, + data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' } + }, ]) ], providers: [ diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index b28a0cf89e..0ddbefd253 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -9,6 +9,7 @@ import { AdminWorkflowModuleModule } from './admin-workflow-page/admin-workflow. import { AdminSearchModule } from './admin-search-page/admin-search.module'; import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; +import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -28,7 +29,8 @@ const ENTRY_COMPONENTS = [ ], declarations: [ AdminCurationTasksComponent, - MetadataImportPageComponent + MetadataImportPageComponent, + BatchImportPageComponent ] }) export class AdminModule { diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index 75a66c822a..91f8d591af 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -25,6 +25,7 @@ import { CoreState } from '../../core-state.model'; export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import'; export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; +export const BATCH_IMPORT_SCRIPT_NAME = 'batch-import'; @Injectable() @dataService(SCRIPT) diff --git a/src/app/menu.resolver.spec.ts b/src/app/menu.resolver.spec.ts index db90b7ea00..f39075ad27 100644 --- a/src/app/menu.resolver.spec.ts +++ b/src/app/menu.resolver.spec.ts @@ -259,6 +259,9 @@ describe('MenuResolver', () => { expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ id: 'import', visible: true, })); + expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ + id: 'import_batch', parentID: 'import', visible: true, + })); expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ id: 'export', visible: true, })); diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index f12079f737..a7c1fe415e 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -448,20 +448,7 @@ export class MenuResolver implements Resolve { * the import scripts exist and the current user is allowed to execute them */ createImportMenuSections() { - const menuList = [ - // TODO: enable this menu item once the feature has been implemented - // { - // id: 'import_batch', - // parentID: 'import', - // active: false, - // visible: true, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.import_batch', - // link: '' - // } as LinkMenuItemModel, - // } - ]; + const menuList = []; menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection)); observableCombineLatest([ @@ -498,6 +485,18 @@ export class MenuResolver implements Resolve { } as LinkMenuItemModel, shouldPersistOnRouteChange: true }); + this.menuService.addSection(MenuID.ADMIN, { + id: 'import_batch', + parentID: 'import', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.import_batch', + link: '/admin/batch-import' + } as LinkMenuItemModel, + shouldPersistOnRouteChange: true + }); }); } diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index ca8343cfad..937608a2c6 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -10,6 +10,7 @@ export enum SelectorActionType { CREATE = 'create', EDIT = 'edit', EXPORT_METADATA = 'export-metadata', + IMPORT_BATCH = 'import-batch', SET_SCOPE = 'set-scope' } diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts new file mode 100644 index 0000000000..576c1ea140 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts @@ -0,0 +1,100 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Community } from '../../../../core/shared/community.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ImportBatchSelectorComponent } from './import-batch-selector.component'; + +describe('ImportBatchSelectorComponent', () => { + let component: ImportBatchSelectorComponent; + let fixture: ComponentFixture; + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' + }); + const mockCollection: Collection = Object.assign(new Collection(), { + id: 'test-collection-1-1', + uuid: 'test-collection-1-1', + name: 'test-collection-1', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-collection-1' + } + ] + } + }); + const mockCommunity = Object.assign(new Community(), { + id: 'test-uuid', + uuid: 'test-uuid', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-community-1' + } + ] + } + }); + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + declarations: [ImportBatchSelectorComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImportBatchSelectorComponent); + component = fixture.componentInstance; + spyOn(component.response, 'emit'); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if item is selected', () => { + beforeEach((done) => { + component.navigate(mockItem).subscribe(() => { + done(); + }); + }); + it('should emit null value', () => { + expect(component.response.emit).toHaveBeenCalledWith(null); + }); + }); + + describe('if collection is selected', () => { + beforeEach((done) => { + component.navigate(mockCollection).subscribe(() => { + done(); + }); + }); + it('should emit collection value', () => { + expect(component.response.emit).toHaveBeenCalledWith(mockCollection); + }); + }); + + describe('if community is selected', () => { + beforeEach((done) => { + component.navigate(mockCommunity).subscribe(() => { + done(); + }); + }); + it('should emit community value', () => { + expect(component.response.emit).toHaveBeenCalledWith(mockCommunity); + }); + }); +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts new file mode 100644 index 0000000000..2fdae23388 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts @@ -0,0 +1,45 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +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 { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; +import { Observable, of } from 'rxjs'; + +/** + * Component to wrap a list of existing dso's inside a modal + * Used to choose a dso from to import metadata of + */ +@Component({ + selector: 'ds-import-batch-selector', + templateUrl: '../dso-selector-modal-wrapper.component.html', +}) +export class ImportBatchSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { + objectType = DSpaceObjectType.DSPACEOBJECT; + selectorTypes = [DSpaceObjectType.COLLECTION, DSpaceObjectType.COMMUNITY]; + action = SelectorActionType.IMPORT_BATCH; + /** + * An event fired when the modal is closed + */ + @Output() + response = new EventEmitter(); + + constructor(protected activeModal: NgbActiveModal, + protected route: ActivatedRoute) { + super(activeModal, route); + } + + /** + * If the dso is a collection or community: + */ + navigate(dso: DSpaceObject): Observable { + if (dso instanceof Collection || dso instanceof Community) { + this.response.emit(dso); + return of(null); + } + this.response.emit(null); + return of(null); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9036ff98c5..5e78796e24 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -24,6 +24,9 @@ import { ConfirmationModalComponent } from './confirmation-modal/confirmation-mo import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; +import { + ImportBatchSelectorComponent +} from './dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component'; import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { ItemListElementComponent } from './object-list/item-list-element/item-types/item/item-list-element.component'; import { EnumKeysPipe } from './utils/enum-keys-pipe'; @@ -468,6 +471,7 @@ const COMPONENTS = [ CollectionDropdownComponent, EntityDropdownComponent, ExportMetadataSelectorComponent, + ImportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, AuthorizedCollectionSelectorComponent, @@ -545,6 +549,7 @@ const ENTRY_COMPONENTS = [ BitstreamRequestACopyPageComponent, CurationFormComponent, ExportMetadataSelectorComponent, + ImportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, SidebarSearchListElementComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bd40bc642c..171dbaa332 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -542,28 +542,45 @@ "admin.metadata-import.breadcrumbs": "Import Metadata", + "admin.batch-import.breadcrumbs": "Import Batch", + "admin.metadata-import.title": "Import Metadata", + "admin.batch-import.title": "Import Batch", + "admin.metadata-import.page.header": "Import Metadata", + "admin.batch-import.page.header": "Import Batch", + "admin.metadata-import.page.help": "You can drop or browse CSV files that contain batch metadata operations on files here", + "admin.batch-import.page.help": "You can drop or browse ZIP files that contain batch operations on files here", + "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", + "admin.batch-import.page.dropMsg": "Drop a batch ZIP to import", + "admin.metadata-import.page.dropMsgReplace": "Drop to replace the metadata CSV to import", + "admin.batch-import.page.dropMsgReplace": "Drop to replace the batch ZIP to import", + "admin.metadata-import.page.button.return": "Back", "admin.metadata-import.page.button.proceed": "Proceed", + "admin.metadata-import.page.button.select-collection": "Select Collection", + "admin.metadata-import.page.error.addFile": "Select file first!", + "admin.batch-import.page.error.addFile": "Select Zip file first!", + "admin.metadata-import.page.validateOnly": "Validate Only", "admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.", + "admin.batch-import.page.validateOnly.hint": "When selected, the uploaded ZIP will be validated. You will receive a report of detected changes, but no changes will be saved.", - + "admin.batch-import.page.remove": "remove", "auth.errors.invalid-user": "Invalid email address or password.", @@ -640,7 +657,7 @@ "bitstream-request-a-copy.alert.canDownload2": "here", "bitstream-request-a-copy.header": "Request a copy of the file", - + "bitstream-request-a-copy.intro": "Enter the following information to request a copy for the following item: ", "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", @@ -1345,6 +1362,8 @@ "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.import-batch.dspaceobject.head": "Import batch from", + "dso-selector.no-results": "No {{ type }} found", "dso-selector.placeholder": "Search for a {{ type }}", @@ -2984,7 +3003,7 @@ "process.detail.create" : "Create similar process", "process.detail.actions": "Actions", - + "process.detail.delete.button": "Delete process", "process.detail.delete.header": "Delete process", @@ -3037,7 +3056,7 @@ "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", - + "profile.breadcrumbs": "Update Profile",