diff --git a/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts b/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts
new file mode 100644
index 0000000000..7d22bf0b61
--- /dev/null
+++ b/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts
@@ -0,0 +1,8 @@
+/*
+ * Object model for the data returned by the REST API to present minted identifiers in a submission section
+ */
+export interface WorkspaceitemSectionIdentifiersObject {
+ doi?: string
+ handle?: string
+ otherIdentifiers?: string[]
+}
diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts
index 1112d740ed..f50c3ac67c 100644
--- a/src/app/core/submission/models/workspaceitem-sections.model.ts
+++ b/src/app/core/submission/models/workspaceitem-sections.model.ts
@@ -3,6 +3,7 @@ import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.mod
import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model';
import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model';
import { WorkspaceitemSectionCcLicenseObject } from './workspaceitem-section-cc-license.model';
+import {WorkspaceitemSectionIdentifiersObject} from "./workspaceitem-section-identifiers.model";
import { WorkspaceitemSectionSherpaPoliciesObject } from './workspaceitem-section-sherpa-policies.model';
/**
@@ -23,4 +24,5 @@ export type WorkspaceitemSectionDataType
| WorkspaceitemSectionCcLicenseObject
| WorkspaceitemSectionAccessesObject
| WorkspaceitemSectionSherpaPoliciesObject
+ | WorkspaceitemSectionIdentifiersObject
| string;
diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.html b/src/app/submission/sections/identifiers/section-identifiers.component.html
new file mode 100644
index 0000000000..1c78119931
--- /dev/null
+++ b/src/app/submission/sections/identifiers/section-identifiers.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
{{'submission.sections.identifiers.info' | translate}}
+
+
+
+ - {{'submission.sections.identifiers.no_handle' | translate}}
+
+
+ - {{'submission.sections.identifiers.handle_label' | translate}}{{identifierData.handle}}
+
+
+
+ - {{'submission.sections.identifiers.no_doi' | translate}}
+
+
+ - {{'submission.sections.identifiers.doi_label' | translate}}
+ {{identifierData.doi}}
+
+
+
+ 0)">
+ - {{'submission.sections.identifiers.otherIdentifiers_label' | translate}}
+ {{identifierData.otherIdentifiers.join(',')}}
+
+
+
+
+
diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts
new file mode 100644
index 0000000000..378ec911c7
--- /dev/null
+++ b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts
@@ -0,0 +1,247 @@
+import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+import { NgxPaginationModule } from 'ngx-pagination';
+import { cold } from 'jasmine-marbles';
+import {Observable, of as observableOf} from 'rxjs';
+import { TranslateModule } from '@ngx-translate/core';
+
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
+import { createTestComponent } from '../../../shared/testing/utils.test';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { SubmissionService } from '../../submission.service';
+import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub';
+import { SectionsService } from '../sections.service';
+import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub';
+import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
+import { getMockFormOperationsService } from '../../../shared/mocks/form-operations-service.mock';
+import { getMockFormService } from '../../../shared/mocks/form-service.mock';
+import { FormService } from '../../../shared/form/form.service';
+import { SubmissionFormsConfigService } from '../../../core/config/submission-forms-config.service';
+import { SectionDataObject } from '../models/section-data.model';
+import { SectionsType } from '../sections-type';
+import {mockSectionsData, mockSubmissionCollectionId, mockSubmissionId} from '../../../shared/mocks/submission.mock';
+import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
+import { SubmissionSectionIdentifiersComponent } from './section-identifiers.component';
+import { CollectionDataService } from '../../../core/data/collection-data.service';
+import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
+import { SectionFormOperationsService } from '../form/section-form-operations.service';
+import { SubmissionScopeType } from '../../../core/submission/submission-scope-type';
+import { License } from '../../../core/shared/license.model';
+import { Collection } from '../../../core/shared/collection.model';
+import { ObjNgFor } from '../../../shared/utils/object-ngfor.pipe';
+import { VarDirective } from '../../../shared/utils/var.directive';
+import { WorkspaceitemSectionIdentifiersObject } from '../../../core/submission/models/workspaceitem-section-identifiers.model';
+import { Item } from '../../../core/shared/item.model';
+import { PaginationService } from '../../../core/pagination/pagination.service';
+import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
+
+function getMockSubmissionFormsConfigService(): SubmissionFormsConfigService {
+ return jasmine.createSpyObj('FormOperationsService', {
+ getConfigAll: jasmine.createSpy('getConfigAll'),
+ getConfigByHref: jasmine.createSpy('getConfigByHref'),
+ getConfigByName: jasmine.createSpy('getConfigByName'),
+ getConfigBySearch: jasmine.createSpy('getConfigBySearch')
+ });
+}
+
+function getMockCollectionDataService(): CollectionDataService {
+ return jasmine.createSpyObj('CollectionDataService', {
+ findById: jasmine.createSpy('findById'),
+ findByHref: jasmine.createSpy('findByHref')
+ });
+}
+
+const mockItem = Object.assign(new Item(), {
+ id: 'fake-match-id',
+ handle: 'fake/handle',
+ metadata: {
+ 'dc.title': [
+ {
+ language: null,
+ value: 'mockmatch'
+ }
+ ]
+ },
+});
+
+// Mock identifier data to use with tests
+const identifierData: WorkspaceitemSectionIdentifiersObject = {
+ doi: 'https://doi.org/10.33515/dspace/1',
+ handle: '123456789/999',
+ otherIdentifiers: ['123-123-123', 'ANBX-159']
+};
+
+// Mock section object to use with tests
+const sectionObject: SectionDataObject = {
+ config: 'https://dspace.org/api/config/submissionforms/identifiers',
+ mandatory: true,
+ opened: true,
+ data: identifierData,
+ errorsToShow: [],
+ serverValidationErrors: [],
+ header: 'submission.sections.submit.progressbar.identifiers',
+ id: 'identifiers',
+ sectionType: SectionsType.Identifiers,
+ sectionVisibility: null
+};
+
+describe('SubmissionSectionIdentifiersComponent test suite', () => {
+ let comp: SubmissionSectionIdentifiersComponent;
+ let compAsAny: any;
+ let fixture: ComponentFixture;
+ let submissionServiceStub: any = new SubmissionServiceStub();
+ const sectionsServiceStub: any = new SectionsServiceStub();
+ let formService: any;
+ let formOperationsService: any;
+ let formBuilderService: any;
+ let collectionDataService: any;
+
+ const submissionId = mockSubmissionId;
+ const collectionId = mockSubmissionCollectionId;
+ const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
+ add: jasmine.createSpy('add'),
+ replace: jasmine.createSpy('replace'),
+ remove: jasmine.createSpy('remove'),
+ });
+
+ const licenseText = 'License text';
+ const mockCollection = Object.assign(new Collection(), {
+ name: 'Community 1-Collection 1',
+ id: collectionId,
+ metadata: [
+ {
+ key: 'dc.title',
+ language: 'en_US',
+ value: 'Community 1-Collection 1'
+ }],
+ license: createSuccessfulRemoteDataObject$(Object.assign(new License(), { text: licenseText }))
+ });
+ const paginationService = new PaginationServiceStub();
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ BrowserModule,
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ NgxPaginationModule,
+ NoopAnimationsModule,
+ TranslateModule.forRoot(),
+ ],
+ declarations: [
+ SubmissionSectionIdentifiersComponent,
+ TestComponent,
+ ObjNgFor,
+ VarDirective,
+ ],
+ providers: [
+ { provide: CollectionDataService, useValue: getMockCollectionDataService() },
+ { provide: SectionFormOperationsService, useValue: getMockFormOperationsService() },
+ { provide: FormService, useValue: getMockFormService() },
+ { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
+ { provide: SubmissionFormsConfigService, useValue: getMockSubmissionFormsConfigService() },
+ { provide: NotificationsService, useClass: NotificationsServiceStub },
+ { provide: SectionsService, useClass: SectionsServiceStub },
+ { provide: SubmissionService, useClass: SubmissionServiceStub },
+ { provide: 'collectionIdProvider', useValue: collectionId },
+ { provide: 'sectionDataProvider', useValue: sectionObject },
+ { provide: 'submissionIdProvider', useValue: submissionId },
+ { provide: PaginationService, useValue: paginationService },
+ ChangeDetectorRef,
+ FormBuilderService,
+ SubmissionSectionIdentifiersComponent
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).compileComponents().then();
+ }));
+
+ // First test to check the correct component creation
+ describe('', () => {
+ let testComp: TestComponent;
+ let testFixture: ComponentFixture;
+
+ // synchronous beforeEach
+ beforeEach(() => {
+ sectionsServiceStub.isSectionReadOnly.and.returnValue(observableOf(false));
+ sectionsServiceStub.getSectionErrors.and.returnValue(observableOf([]));
+ sectionsServiceStub.getSectionData.and.returnValue(observableOf(identifierData));
+ const html = ``;
+ testFixture = createTestComponent(html, TestComponent) as ComponentFixture;
+ testComp = testFixture.componentInstance;
+ });
+
+ afterEach(() => {
+ testFixture.destroy();
+ });
+
+ it('should create SubmissionSectionIdentifiersComponent', inject([SubmissionSectionIdentifiersComponent], (idComp: SubmissionSectionIdentifiersComponent) => {
+ expect(idComp).toBeDefined();
+ }));
+ });
+
+ describe('', () => {
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SubmissionSectionIdentifiersComponent);
+ comp = fixture.componentInstance;
+ compAsAny = comp;
+ submissionServiceStub = TestBed.inject(SubmissionService);
+ formService = TestBed.inject(FormService);
+ formBuilderService = TestBed.inject(FormBuilderService);
+ formOperationsService = TestBed.inject(SectionFormOperationsService);
+ collectionDataService = TestBed.inject(CollectionDataService);
+ compAsAny.pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionObject.id);
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ comp = null;
+ compAsAny = null;
+ });
+
+ // Test initialisation of the submission section
+ it('Should init section properly', () => {
+ collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection));
+ sectionsServiceStub.getSectionErrors.and.returnValue(observableOf([]));
+ sectionsServiceStub.isSectionReadOnly.and.returnValue(observableOf(false));
+ compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkspaceItem);
+ spyOn(comp, 'getSectionStatus').and.returnValue(observableOf(true));
+ spyOn(comp, 'getIdentifierData').and.returnValue(observableOf(identifierData));
+ expect(comp.isLoading).toBeTruthy();
+ comp.onSectionInit();
+ fixture.detectChanges();
+ expect(comp.isLoading).toBeFalsy();
+ });
+
+ // The following tests look for proper logic in the getSectionStatus() implementation
+ // These are very simple as we don't really have a 'false' state unless we're still loading
+ it('Should return TRUE if the isLoading is FALSE', () => {
+ compAsAny.isLoading = false;
+ expect(compAsAny.getSectionStatus()).toBeObservable(cold('(a|)', {
+ a: true
+ }));
+ });
+ it('Should return FALSE if the identifier data is missing handle', () => {
+ compAsAny.isLoadin = true;
+ expect(compAsAny.getSectionStatus()).toBeObservable(cold('(a|)', {
+ a: false
+ }));
+ });
+ });
+
+});
+
+// declare a test component
+@Component({
+ selector: 'ds-test-cmp',
+ template: ``
+})
+class TestComponent {
+
+}
diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.ts b/src/app/submission/sections/identifiers/section-identifiers.component.ts
new file mode 100644
index 0000000000..9f5d65c2aa
--- /dev/null
+++ b/src/app/submission/sections/identifiers/section-identifiers.component.ts
@@ -0,0 +1,138 @@
+import {ChangeDetectionStrategy, Component, Inject, Input} from '@angular/core';
+
+import { Observable, of as observableOf, Subscription } from 'rxjs';
+import { TranslateService } from '@ngx-translate/core';
+import { SectionsType } from '../sections-type';
+import { SectionModelComponent } from '../models/section.model';
+import { renderSectionFor } from '../sections-decorator';
+import { SectionDataObject } from '../models/section-data.model';
+import { SubmissionService } from '../../submission.service';
+import { AlertType } from '../../../shared/alert/aletr-type';
+import { SectionsService } from '../sections.service';
+import { WorkspaceitemSectionIdentifiersObject } from '../../../core/submission/models/workspaceitem-section-identifiers.model';
+import { PaginationService } from '../../../core/pagination/pagination.service';
+import { SubmissionVisibility } from '../../utils/visibility.util';
+import {distinctUntilChanged, filter, map} from 'rxjs/operators';
+import {hasValue} from '../../../shared/empty.util';
+
+/**
+ * This simple component displays DOI, handle and other identifiers that are already minted for the item in
+ * a workflow / submission section.
+ * ShowMintIdentifierStep will attempt to reserve an identifier before injecting result data for this component.
+ *
+ * @author Kim Shepherd
+ */
+@Component({
+ selector: 'ds-submission-section-identifiers',
+ templateUrl: './section-identifiers.component.html',
+ changeDetection: ChangeDetectionStrategy.Default
+})
+
+@renderSectionFor(SectionsType.Identifiers)
+export class SubmissionSectionIdentifiersComponent extends SectionModelComponent {
+ /**
+ * The Alert categories.
+ * @type {AlertType}
+ */
+ public AlertTypeEnum = AlertType;
+
+ /**
+ * Variable to track if the section is loading.
+ * @type {boolean}
+ */
+ public isLoading = true;
+
+ /**
+ * Observable identifierData subject
+ * @type {Observable}
+ */
+ public identifierData$: Observable = new Observable();
+
+ /**
+ * Array to track all subscriptions and unsubscribe them onDestroy
+ * @type {Array}
+ */
+ protected subs: Subscription[] = [];
+ public subbedIdentifierData: WorkspaceitemSectionIdentifiersObject;
+
+ /**
+ * Initialize instance variables.
+ *
+ * @param {PaginationService} paginationService
+ * @param {TranslateService} translate
+ * @param {SectionsService} sectionService
+ * @param {SubmissionService} submissionService
+ * @param {string} injectedCollectionId
+ * @param {SectionDataObject} injectedSectionData
+ * @param {string} injectedSubmissionId
+ */
+ constructor(protected translate: TranslateService,
+ protected sectionService: SectionsService,
+ protected submissionService: SubmissionService,
+ @Inject('collectionIdProvider') public injectedCollectionId: string,
+ @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject,
+ @Inject('submissionIdProvider') public injectedSubmissionId: string) {
+ super(injectedCollectionId, injectedSectionData, injectedSubmissionId);
+ }
+
+ ngOnInit() {
+ //this.identifierData$ = {} as Observable;
+ /*
+ this.subs.push(
+ this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType).pipe(
+ filter((identiferData: WorkspaceitemSectionIdentifiersObject) => hasValue()),
+ distinctUntilChanged()).subscribe((identifierData: WorkspaceitemSectionIdentifiersObject) => {
+ this.subbedIdentifierData = identifierData;
+ }
+ )
+ )*/
+ super.ngOnInit();
+ }
+
+ /**
+ * Initialize all instance variables and retrieve configuration.
+ */
+ onSectionInit() {
+ this.isLoading = false;
+ this.identifierData$ = this.getIdentifierData();
+ }
+
+ /**
+ * Check if identifier section has read-only visibility
+ */
+ isReadOnly(): boolean {
+ return SubmissionVisibility.isReadOnly(
+ this.sectionData.sectionVisibility,
+ this.submissionService.getSubmissionScope()
+ );
+ }
+
+ /**
+ * Unsubscribe from all subscriptions, if needed.
+ */
+ onSectionDestroy(): void {
+ return;
+ }
+
+ /**
+ * Get section status. Because this simple component never requires human interaction, this is basically
+ * always going to be the opposite of "is this section still loading". This is not the place for API response
+ * error checking but determining whether the step can 'proceed'.
+ *
+ * @return Observable
+ * the section status
+ */
+ public getSectionStatus(): Observable {
+ return observableOf(!this.isLoading);
+ }
+
+ /**
+ * Get identifier data (from the REST service) as a simple object with doi, handle, otherIdentifiers variables
+ * and as an observable so it can update in real-time.
+ */
+ getIdentifierData() {
+ return this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) as
+ Observable;
+ }
+
+}
diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts
index 6b6f839b7c..6fb7380822 100644
--- a/src/app/submission/sections/sections-type.ts
+++ b/src/app/submission/sections/sections-type.ts
@@ -7,4 +7,5 @@ export enum SectionsType {
collection = 'collection',
AccessesCondition = 'accessCondition',
SherpaPolicies = 'sherpaPolicy',
+ Identifiers = 'identifiers',
}
diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts
index cab4f19c33..91f782225a 100644
--- a/src/app/submission/submission.module.ts
+++ b/src/app/submission/submission.module.ts
@@ -65,6 +65,7 @@ import {
MetadataInformationComponent
} from './sections/sherpa-policies/metadata-information/metadata-information.component';
import { SectionFormOperationsService } from './sections/form/section-form-operations.service';
+import {SubmissionSectionIdentifiersComponent} from './sections/identifiers/section-identifiers.component';
const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
@@ -93,6 +94,7 @@ const DECLARATIONS = [
SubmissionSectionUploadFileComponent,
SubmissionSectionUploadFileEditComponent,
SubmissionSectionUploadFileViewComponent,
+ SubmissionSectionIdentifiersComponent,
SubmissionImportExternalComponent,
ThemedSubmissionImportExternalComponent,
SubmissionImportExternalSearchbarComponent,
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index f68c0ff2ce..3416a7cf9d 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -4282,7 +4282,17 @@
"submission.sections.general.sections_not_valid": "There are incomplete sections.",
+ "submission.sections.identifiers.info": "The following identifiers will be created for your item:",
+ "submission.sections.identifiers.no_handle": "No handles have been minted for this item.",
+
+ "submission.sections.identifiers.no_doi": "No DOIs have been minted for this item.",
+
+ "submission.sections.identifiers.handle_label": "Handle: ",
+
+ "submission.sections.identifiers.doi_label": "DOI: ",
+
+ "submission.sections.identifiers.otherIdentifiers_label": "Other identifiers: ",
"submission.sections.submit.progressbar.accessCondition": "Item access conditions",