diff --git a/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts b/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts new file mode 100644 index 00000000000..9233780be86 --- /dev/null +++ b/src/app/core/submission/models/workspaceitem-section-deduplication.model.ts @@ -0,0 +1,21 @@ +import { Item } from '../../shared/item.model'; + +export interface WorkspaceitemSectionDetectDuplicateObject { + matches: { + [itemId: string]: DetectDuplicateMatch; + }; +} + +export interface DetectDuplicateMatch { + submitterDecision?: string; // [reject|verify] + submitterNote?: string; + submitterTime?: string; // (readonly) + + workflowDecision?: string; // [reject|verify] + workflowNote?: string; + workflowTime?: string; // (readonly) + + adminDecision?: string; + + matchObject?: Item; +} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index 1112d740ed9..3f548102a3c 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -4,6 +4,7 @@ import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-licen import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; import { WorkspaceitemSectionCcLicenseObject } from './workspaceitem-section-cc-license.model'; import { WorkspaceitemSectionSherpaPoliciesObject } from './workspaceitem-section-sherpa-policies.model'; +import { WorkspaceitemSectionDetectDuplicateObject } from './workspaceitem-section-deduplication.model'; /** * An interface to represent submission's section object. @@ -23,4 +24,5 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionCcLicenseObject | WorkspaceitemSectionAccessesObject | WorkspaceitemSectionSherpaPoliciesObject + | WorkspaceitemSectionDetectDuplicateObject | string; diff --git a/src/app/my-dspace-page/my-dspace-search.module.ts b/src/app/my-dspace-page/my-dspace-search.module.ts index 1ce39991b30..054df8e0d34 100644 --- a/src/app/my-dspace-page/my-dspace-search.module.ts +++ b/src/app/my-dspace-page/my-dspace-search.module.ts @@ -20,11 +20,17 @@ import { ResearchEntitiesModule } from '../entity-groups/research-entities/resea import { ItemSubmitterComponent } from '../shared/object-collection/shared/mydspace-item-submitter/item-submitter.component'; import { ItemDetailPreviewComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; import { ItemDetailPreviewFieldComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; -import { ItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; -import { ThemedItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +//import { ItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; +//import { ThemedItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; import { MyDSpaceItemStatusComponent } from '../shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { MyDSpaceActionsModule } from '../shared/mydspace-actions/mydspace-actions.module'; +import { + ThemedItemListPreviewComponent +} from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { + ItemListPreviewComponent +} from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; const ENTRY_COMPONENTS = [ WorkspaceItemSearchResultListElementComponent, @@ -39,6 +45,8 @@ const ENTRY_COMPONENTS = [ ClaimedTaskSearchResultDetailElementComponent, PoolSearchResultDetailElementComponent, ItemSearchResultListElementSubmissionComponent, + //ItemListPreviewComponent, + //ThemedItemListPreviewComponent, ]; const DECLARATIONS = [ @@ -62,7 +70,10 @@ const DECLARATIONS = [ ], declarations: [ ...DECLARATIONS, - ] + ], + exports: [ + ...DECLARATIONS, + ] }) /** diff --git a/src/app/shared/mocks/mock-detect-duplicate-service.ts b/src/app/shared/mocks/mock-detect-duplicate-service.ts new file mode 100644 index 00000000000..96b94fb70b8 --- /dev/null +++ b/src/app/shared/mocks/mock-detect-duplicate-service.ts @@ -0,0 +1,14 @@ +import { DetectDuplicateService } from '../../submission/sections/detect-duplicate/detect-duplicate.service'; + +/** + * Mock for [[DetectDuplicateService]] + */ +export function getMockDetectDuplicateService(): +DetectDuplicateService { + return jasmine.createSpyObj('DetectDuplicateService', { + getDuplicateMatches: jasmine.createSpy('getDuplicateMatches'), + getDuplicateTotalMatches: jasmine.createSpy('getDuplicateTotalMatches'), + saveDuplicateDecision: jasmine.createSpy('saveDuplicateDecision'), + getDuplicateMatchesByScope: jasmine.createSpy('getDuplicateMatchesByScope'), + }); +} diff --git a/src/app/shared/mocks/submission.mock.ts b/src/app/shared/mocks/submission.mock.ts index b90cababcbe..c42a630ec98 100644 --- a/src/app/shared/mocks/submission.mock.ts +++ b/src/app/shared/mocks/submission.mock.ts @@ -299,6 +299,16 @@ export const mockSubmissionRestResponse = [ self: { href: 'https://rest.api/dspace-spring-rest/api/config/submissionsections/license' }, config: '' }, + }, + { + header : 'submit.progressbar.detect-duplicate', + mandatory : true, + sectionType : 'detect-duplicate', + type : 'submissionsection', + _links : { + self : { href: 'https://dspacecris7.4science.cloud/server/api/config/submissionsections/detect-duplicate' }, + config: '' + } } ], name: 'traditional', @@ -564,6 +574,9 @@ export const mockSubmissionObject = { }, upload: { files: [] + }, + 'detect-duplicate': { + matches: {} } }, errors: [ @@ -1002,189 +1015,2011 @@ export const mockSubmissionDefinition: SubmissionDefinitionsModel = { }, } as any; -export const mockSubmissionState: SubmissionObjectState = Object.assign({}, { - 826: { - collection: mockSubmissionCollectionId, - definition: 'traditional', - selfUrl: mockSubmissionSelfUrl, - activeSection: null, - sections: { - extraction: { - config: '', - mandatory: true, - sectionType: 'utils', - visibility: { - main: 'HIDDEN', - other: 'HIDDEN' - }, - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any, - 'collection': { - config: '', - mandatory: true, - sectionType: 'collection', - visibility: { - main: 'HIDDEN', - other: 'HIDDEN' - }, - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any, - 'traditionalpageone': { - header: 'submit.progressbar.describe.stepone', - config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpageone', - mandatory: true, - sectionType: 'submission-form', - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - formId: '2_traditionalpageone', - isLoading: false, - isValid: false, - removePending: false - } as any, - 'traditionalpagetwo': { - header: 'submit.progressbar.describe.steptwo', - config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpagetwo', - mandatory: false, - sectionType: 'submission-form', - collapsed: false, - enabled: false, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any, - 'detect-duplicate': { - header: 'submit.progressbar.detect-duplicate', - config: '', - mandatory: true, - sectionType: 'detect-duplicate', - collapsed: false, - enabled: true, - data: { - matches: {} - }, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any, - 'upload': { - header: 'submit.progressbar.upload', - config: 'https://rest.api/dspace-spring-rest/api/config/submissionuploads/upload', - mandatory: true, - sectionType: 'upload', - collapsed: false, - enabled: true, - data: { - files: [] - }, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any, - 'license': { - header: 'submit.progressbar.license', - config: '', - mandatory: true, - sectionType: 'license', - visibility: { - main: null, - other: 'READONLY' - }, - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false, - removePending: false - } as any - }, - isLoading: false, - savePending: false, - depositPending: false - } -}); -export const mockSubmissionStateWithDuplicate: SubmissionObjectState = Object.assign({}, { - 826: { - collection: mockSubmissionCollectionId, - definition: 'traditional', - selfUrl: mockSubmissionSelfUrl, - activeSection: null, - sections: { - 'extraction': { - config: '', - mandatory: true, - sectionType: 'utils', - visibility: { - main: 'HIDDEN', - other: 'HIDDEN' - }, - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false - } as any, - collection: { - config: '', - mandatory: true, - sectionType: 'collection', - visibility: { - main: 'HIDDEN', - other: 'HIDDEN' - }, - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false - } as any, - traditionalpageone: { - header: 'submit.progressbar.describe.stepone', - config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpageone', - mandatory: true, - sectionType: 'submission-form', - collapsed: false, - enabled: true, - data: {}, - errorsToShow: [], - formId: '2_traditionalpageone', - isLoading: false, - isValid: false - } as any, - traditionalpagetwo: { - header: 'submit.progressbar.describe.steptwo', - config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpagetwo', - mandatory: false, - sectionType: 'submission-form', - collapsed: false, - enabled: false, - data: {}, - errorsToShow: [], - isLoading: false, - isValid: false - } as any, - upload: { +export const mockDeduplicationMatches = { + '78ca1d06-cce7-4ee9-abda-46440d9b0bb7' : { + submitterDecision: null, + workflowDecision: 'reject', + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + uuid: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'ZHANG, Xiaowang', + language: null, + authority: 'rp01733', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Erni, Rolf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, Gustaaf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '182400', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '09359648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '15214095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'The diamond nucleation step is critical for the chemical vapor deposition (CVD) of diamond on non-diamond substrates, i.e., for heteroepitaxial as well as polycrystalline growth on non-diamond (foreign) substrates. This process has been studied intensively over the past 20 years. [1-8] In general, diamond CVD growth on foreign substrates requires artificial formation of diamond nucleation sites on the substrate\'s surface. The high surface energy of diamond, [9] usually prevents direct, heterogeneous diamond nucleation from the gas phase, hence diamond growth cannot be initiated without this critical nucleation step. [5,8] As for the subsequently occurring diamond growth, it is assumed that atomic hydrogen is the only essential mediator required for stabilizing the diamond phase. Nonetheless, it was recently suggested that diamond can also grow in bulk, i.e., in a solid state environment, such as the sub-surface of silicon, if carbon atoms are sub-implanted by low energetic beams and transformed into diamond grains. [10,11] In this communication, we show that diffusion based transport of carbon atoms from diamond seeds through an interlayer is yet another mechanism by which diamond nuclei can be formed. This process opens further possibilities for the LPLTgrowth of synthetic diamond on a variety of substrates and gives access to new applications for nanocrystal-line diamonds (NCD), where diamond-like carbon and amorphous carbon are already applicable. [12] Carbon transport and subsequently occurring sp 3 bonded carbon cluster formation originates from dissolving so-called ultra-dispersed nanodia-mond particles (UDDs) of 5-10 nm size, which are readily prepared in form of a monolayer beneath a TiO 2 sol-gel thin film on silicon substrate surfaces. [13] Being able to also initiate diamond nucleation, UDDs have become a commonly used tool for CDV diamond seeding and initiation of CVD growth. [14] UDD seeding does not require additional diamond nucleation since the diamond film can grow epitaxially on the UDD grains during the CVD process, which leads to ultra-thin films (30-50 nm) with full surface coverage. In this work NCD film nucleation and growth were studied using UDD particles that are buried under a sol-gel TiO 2 layer spin-coated on UDD seeded silicon substrates. It was observed that when immersed in a conventional H 2 /CH 4 microwave plasma that is commonly used for CVD diamond growth, a partial dissolution of the UDD grain into the TiO 2 occurs. Subsequent carbon diffusion through the 5-10 nm thick TiO 2 layer leads to growth and transformation of the carbon atoms into sp 3 bonded clusters, i.e., diamond nuclei. This was studied by high-resolution transmission electron microscopy (HRTEM), energy-filtered TEM (EFTEM), and electron energy loss spectroscopy (EELS). In order to elucidate the diamond nucleation process in more detail, different sample preparation methods were used, three of which are depicted schematically in the insets of Figure 1 and Figure 2. The first method (method I), inset in Figure 1a, shows a TiO 2 layer deposited by sol-gel technique on a bare Si substrate. The TiO 2 precursor solution contained UDD particles in the mixture. Due to the very low thickness of the TiO 2 interlayer, the time that is required for carbon-saturation of the layer should be very short when exposed to the H 2 /CH 4 plasma during the microwave plasma enhanced CVD (MW PE CVD) process. [15,16] However, due to a too low UDD concentration used in the TiO 2 precursor solution and problems experienced with the homogenous dispersion of the powder, no homogeneous film was obtained after 60 min of CVD growth. Specifically, it appears that the UDD particles float on top of the precursor material and cluster together. This leads, after the formation of the sol-gel layer, to the creation of areas with no seeds and areas with seeds floating on top, as shown in Figure 1a. Furthermore, Figure 1b indicates that no diamond film growth takes place where no diamond seeds were present prior to the growth process, hence proving the necessity of UDD pre-treatment of the TiO 2 coated Si substrates. If no UDD seeding is used at all (method II), i.e., bare TiO 2 is deposited directly onto the Si substrate as shown schematically in COMMUNICATION www.advmat.de', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'B-NCD-layer', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'PID-control', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'temperature regulator', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Mono-Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'journal_article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '673', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/pssa.201000291', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Pdf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.eissn', + value: '1521-4095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'Advanced Materials', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Open Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'With Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; ZHANG, Xiaowang; Erni, Rolf; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, Gustaaf (182400) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:16:02.300+0000', + type: 'item' + } + }, + 'ebae3c99-f438-4b65-879b-1eea7a9e0324': { + submitterDecision: 'reject', + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: 'ebae3c99-f438-4b65-879b-1eea7a9e0324', + uuid: 'ebae3c99-f438-4b65-879b-1eea7a9e0324', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'ZHANG, Xiaowang', + language: null, + authority: 'rp01733', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Erni, Rolf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, Gustaaf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '182400', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '09359648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '15214095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'The diamond nucleation step is critical for the chemical vapor deposition (CVD) of diamond on non-diamond substrates, i.e., for heteroepitaxial as well as polycrystalline growth on non-diamond (foreign) substrates. This process has been studied intensively over the past 20 years. [1-8] In general, diamond CVD growth on foreign substrates requires artificial formation of diamond nucleation sites on the substrate\'s surface. The high surface energy of diamond, [9] usually prevents direct, heterogeneous diamond nucleation from the gas phase, hence diamond growth cannot be initiated without this critical nucleation step. [5,8] As for the subsequently occurring diamond growth, it is assumed that atomic hydrogen is the only essential mediator required for stabilizing the diamond phase. Nonetheless, it was recently suggested that diamond can also grow in bulk, i.e., in a solid state environment, such as the sub-surface of silicon, if carbon atoms are sub-implanted by low energetic beams and transformed into diamond grains. [10,11] In this communication, we show that diffusion based transport of carbon atoms from diamond seeds through an interlayer is yet another mechanism by which diamond nuclei can be formed. This process opens further possibilities for the LPLTgrowth of synthetic diamond on a variety of substrates and gives access to new applications for nanocrystal-line diamonds (NCD), where diamond-like carbon and amorphous carbon are already applicable. [12] Carbon transport and subsequently occurring sp 3 bonded carbon cluster formation originates from dissolving so-called ultra-dispersed nanodia-mond particles (UDDs) of 5-10 nm size, which are readily prepared in form of a monolayer beneath a TiO 2 sol-gel thin film on silicon substrate surfaces. [13] Being able to also initiate diamond nucleation, UDDs have become a commonly used tool for CDV diamond seeding and initiation of CVD growth. [14] UDD seeding does not require additional diamond nucleation since the diamond film can grow epitaxially on the UDD grains during the CVD process, which leads to ultra-thin films (30-50 nm) with full surface coverage. In this work NCD film nucleation and growth were studied using UDD particles that are buried under a sol-gel TiO 2 layer spin-coated on UDD seeded silicon substrates. It was observed that when immersed in a conventional H 2 /CH 4 microwave plasma that is commonly used for CVD diamond growth, a partial dissolution of the UDD grain into the TiO 2 occurs. Subsequent carbon diffusion through the 5-10 nm thick TiO 2 layer leads to growth and transformation of the carbon atoms into sp 3 bonded clusters, i.e., diamond nuclei. This was studied by high-resolution transmission electron microscopy (HRTEM), energy-filtered TEM (EFTEM), and electron energy loss spectroscopy (EELS). In order to elucidate the diamond nucleation process in more detail, different sample preparation methods were used, three of which are depicted schematically in the insets of Figure 1 and Figure 2. The first method (method I), inset in Figure 1a, shows a TiO 2 layer deposited by sol-gel technique on a bare Si substrate. The TiO 2 precursor solution contained UDD particles in the mixture. Due to the very low thickness of the TiO 2 interlayer, the time that is required for carbon-saturation of the layer should be very short when exposed to the H 2 /CH 4 plasma during the microwave plasma enhanced CVD (MW PE CVD) process. [15,16] However, due to a too low UDD concentration used in the TiO 2 precursor solution and problems experienced with the homogenous dispersion of the powder, no homogeneous film was obtained after 60 min of CVD growth. Specifically, it appears that the UDD particles float on top of the precursor material and cluster together. This leads, after the formation of the sol-gel layer, to the creation of areas with no seeds and areas with seeds floating on top, as shown in Figure 1a. Furthermore, Figure 1b indicates that no diamond film growth takes place where no diamond seeds were present prior to the growth process, hence proving the necessity of UDD pre-treatment of the TiO 2 coated Si substrates. If no UDD seeding is used at all (method II), i.e., bare TiO 2 is deposited directly onto the Si substrate as shown schematically in COMMUNICATION www.advmat.de', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Di-Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'journal_article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '673', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/adma.200802305', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Pdf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.eissn', + value: '1521-4095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'Advanced Materials', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Open Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'With Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; ZHANG, Xiaowang; Erni, Rolf; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, Gustaaf (182400) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:19:26.201+0000', + type: 'item' + } + }, + 'af7ed53f-a967-4033-8c0b-22ece50712b7': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: 'af7ed53f-a967-4033-8c0b-22ece50712b7', + uuid: 'af7ed53f-a967-4033-8c0b-22ece50712b7', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Marc', + language: null, + authority: 'rp02327', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Zhang, L', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'Erni, R', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Bael, MK', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Pawel', + language: null, + authority: 'rp00802', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, G', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '2009', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '0935-9648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Tri-Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'Journal/Magazine Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '+', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/adma.200802305', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Web of Science', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'ADVANCED MATERIALS', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Closed Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'No Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Marc; Zhang, L; Erni, R; WILLIAMS, Oliver; HARDY, An; Van Bael, MK; WAGNER, Pawel; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, G (2009) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T08:39:38.140+0000', + type: 'item' + } + }, + '27bd0e52-d8c1-4239-98aa-52e1fbe68ab9': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '27bd0e52-d8c1-4239-98aa-52e1fbe68ab9', + uuid: '27bd0e52-d8c1-4239-98aa-52e1fbe68ab9', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + handle: '123456789/28372', + metadata: [ { + key: 'dc.date.accessioned', + value: '2019-11-20T10:54:55Z', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.available', + value: '2019-11-20T10:54:55Z', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '2019', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.submitted', + value: '2019-11-20T10:53:16Z', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.uri', + value: 'http://localhost:8080/handle/123456789/28372', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Tetra-Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.faculty', + value: '[ERROR] Error fetching faculty info.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Closed Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.validation', + value: '[ERROR] Error fetching validation info.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'No Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: ' (2019) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.organization', + value: '[ERROR] Error fetching organization info.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: true, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-20T10:54:55.689+0000', + type: 'item' + } + }, + 'd9ac8553-aded-41f4-b7d7-97c48efc523d': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: 'd9ac8553-aded-41f4-b7d7-97c48efc523d', + uuid: 'd9ac8553-aded-41f4-b7d7-97c48efc523d', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + handle: '1942/9650', + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'Zhang, Liang', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'Erni, R.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 600 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, G.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.accessioned', + value: '2009-05-06T13:23:06Z', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '2009', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.citation', + value: 'ADVANCED MATERIALS, 21(6). p. 670-+', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '0935-9648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.uri', + value: 'http://hdl.handle.net/1942/9650', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: 'en', + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'en', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-V C H VERLAG GMBH', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Penta-Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + language: 'en', + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'Journal Contribution', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '+', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.title', + value: 'ADVANCED MATERIALS', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.format.pages', + value: '5', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jcat', + value: 'A1', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.notes', + value: '[Daenen, Michael; Williams, Oliver Aneurin; Hardy, An; Van Bael, Marlies Karolien; Wagner, Patrick; Haenen, Ken; Nesladek, Milos] Hasselt Univ, Inst Mat Res, B-3950 Diepenbeek, Belgium. [Nesladek, Milos] Acad Sci Czech Republ VVI, Inst Phys, Prague 182400, Czech Republic. [Williams, Oliver Aneurin; Hardy, An; Van Bael, Marlies Karolien; Haenen, Ken; Nesladek, Milos] IMEC Vzw, Div IMOMEC, B-3950 Diepenbeek, Belgium. [Hardy, An] XIOS Hogesch Limburg, Dept Ind Sci & Technol, B-3590 Diepenbeek, Belgium. [Zhang, Liang; Erni, Rot; Van Tendeloo, Gustaaf] Univ Antwerp, EMAT, B-2020 Antwerp, Belgium.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.type.refereed', + value: 'Refereed', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.type.specified', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.bibliographicCitation.oldjcat', + value: 'A1', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/adma.200802305', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: '000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'yes', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhasselt', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: null, + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: null, + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: 'no', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.contributor.uhmissing', + value: null, + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.faculty', + value: '[ERROR] Error fetching faculty info.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Closed Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.validation', + value: '[ERROR] Error fetching validation info.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'No Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; Zhang, Liang; Erni, R.; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, G. (2009) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites. In: ADVANCED MATERIALS, 21(6). p. 670-+.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.organization', + value: '[ERROR] Error fetching organization info.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: true, + discoverable: true, + withdrawn: false, + lastModified: '2011-11-25T08:24:48.952+0000', + type: 'item' + } + }, + '5a4d6c2c-588a-4501-b71f-df56ceaebf79': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '5a4d6c2c-588a-4501-b71f-df56ceaebf79', + uuid: '5a4d6c2c-588a-4501-b71f-df56ceaebf79', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.title', + value: 'Diamond Esa-Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Closed Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'No Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2020-01-28T17:10:12.006+0000', + type: 'item' + } + }, + '8aebdea0-97c6-4576-bda5-4a3f974dd7c5': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '8aebdea0-97c6-4576-bda5-4a3f974dd7c5', + uuid: '8aebdea0-97c6-4576-bda5-4a3f974dd7c5', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'ZHANG, Xiaowang', + language: null, + authority: 'rp01733', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Erni, Rolf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, Gustaaf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '182400', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '09359648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '15214095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'The diamond nucleation step is critical for the chemical vapor deposition (CVD) of diamond on non-diamond substrates, i.e., for heteroepitaxial as well as polycrystalline growth on non-diamond (foreign) substrates. This process has been studied intensively over the past 20 years. [1-8] In general, diamond CVD growth on foreign substrates requires artificial formation of diamond nucleation sites on the substrate\'s surface. The high surface energy of diamond, [9] usually prevents direct, heterogeneous diamond nucleation from the gas phase, hence diamond growth cannot be initiated without this critical nucleation step. [5,8] As for the subsequently occurring diamond growth, it is assumed that atomic hydrogen is the only essential mediator required for stabilizing the diamond phase. Nonetheless, it was recently suggested that diamond can also grow in bulk, i.e., in a solid state environment, such as the sub-surface of silicon, if carbon atoms are sub-implanted by low energetic beams and transformed into diamond grains. [10,11] In this communication, we show that diffusion based transport of carbon atoms from diamond seeds through an interlayer is yet another mechanism by which diamond nuclei can be formed. This process opens further possibilities for the LPLTgrowth of synthetic diamond on a variety of substrates and gives access to new applications for nanocrystal-line diamonds (NCD), where diamond-like carbon and amorphous carbon are already applicable. [12] Carbon transport and subsequently occurring sp 3 bonded carbon cluster formation originates from dissolving so-called ultra-dispersed nanodia-mond particles (UDDs) of 5-10 nm size, which are readily prepared in form of a monolayer beneath a TiO 2 sol-gel thin film on silicon substrate surfaces. [13] Being able to also initiate diamond nucleation, UDDs have become a commonly used tool for CDV diamond seeding and initiation of CVD growth. [14] UDD seeding does not require additional diamond nucleation since the diamond film can grow epitaxially on the UDD grains during the CVD process, which leads to ultra-thin films (30-50 nm) with full surface coverage. In this work NCD film nucleation and growth were studied using UDD particles that are buried under a sol-gel TiO 2 layer spin-coated on UDD seeded silicon substrates. It was observed that when immersed in a conventional H 2 /CH 4 microwave plasma that is commonly used for CVD diamond growth, a partial dissolution of the UDD grain into the TiO 2 occurs. Subsequent carbon diffusion through the 5-10 nm thick TiO 2 layer leads to growth and transformation of the carbon atoms into sp 3 bonded clusters, i.e., diamond nuclei. This was studied by high-resolution transmission electron microscopy (HRTEM), energy-filtered TEM (EFTEM), and electron energy loss spectroscopy (EELS). In order to elucidate the diamond nucleation process in more detail, different sample preparation methods were used, three of which are depicted schematically in the insets of Figure 1 and Figure 2. The first method (method I), inset in Figure 1a, shows a TiO 2 layer deposited by sol-gel technique on a bare Si substrate. The TiO 2 precursor solution contained UDD particles in the mixture. Due to the very low thickness of the TiO 2 interlayer, the time that is required for carbon-saturation of the layer should be very short when exposed to the H 2 /CH 4 plasma during the microwave plasma enhanced CVD (MW PE CVD) process. [15,16] However, due to a too low UDD concentration used in the TiO 2 precursor solution and problems experienced with the homogenous dispersion of the powder, no homogeneous film was obtained after 60 min of CVD growth. Specifically, it appears that the UDD particles float on top of the precursor material and cluster together. This leads, after the formation of the sol-gel layer, to the creation of areas with no seeds and areas with seeds floating on top, as shown in Figure 1a. Furthermore, Figure 1b indicates that no diamond film growth takes place where no diamond seeds were present prior to the growth process, hence proving the necessity of UDD pre-treatment of the TiO 2 coated Si substrates. If no UDD seeding is used at all (method II), i.e., bare TiO 2 is deposited directly onto the Si substrate as shown schematically in COMMUNICATION www.advmat.de', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Epta-Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'journal_article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '673', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/adma.200802305', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Pdf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.eissn', + value: '1521-4095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'Advanced Materials', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Open Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'With Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; ZHANG, Xiaowang; Erni, Rolf; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, Gustaaf (182400) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:19:59.566+0000', + type: 'item' + } + }, + '7d69f4c6-82a9-4c48-8c5a-aded913882fd': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '7d69f4c6-82a9-4c48-8c5a-aded913882fd', + uuid: '7d69f4c6-82a9-4c48-8c5a-aded913882fd', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'ZHANG, Xiaowang', + language: null, + authority: 'rp01733', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Erni, Rolf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, Gustaaf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '182400', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '09359648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '15214095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'The diamond nucleation step is critical for the chemical vapor deposition (CVD) of diamond on non-diamond substrates, i.e., for heteroepitaxial as well as polycrystalline growth on non-diamond (foreign) substrates. This process has been studied intensively over the past 20 years. [1-8] In general, diamond CVD growth on foreign substrates requires artificial formation of diamond nucleation sites on the substrate\'s surface. The high surface energy of diamond, [9] usually prevents direct, heterogeneous diamond nucleation from the gas phase, hence diamond growth cannot be initiated without this critical nucleation step. [5,8] As for the subsequently occurring diamond growth, it is assumed that atomic hydrogen is the only essential mediator required for stabilizing the diamond phase. Nonetheless, it was recently suggested that diamond can also grow in bulk, i.e., in a solid state environment, such as the sub-surface of silicon, if carbon atoms are sub-implanted by low energetic beams and transformed into diamond grains. [10,11] In this communication, we show that diffusion based transport of carbon atoms from diamond seeds through an interlayer is yet another mechanism by which diamond nuclei can be formed. This process opens further possibilities for the LPLTgrowth of synthetic diamond on a variety of substrates and gives access to new applications for nanocrystal-line diamonds (NCD), where diamond-like carbon and amorphous carbon are already applicable. [12] Carbon transport and subsequently occurring sp 3 bonded carbon cluster formation originates from dissolving so-called ultra-dispersed nanodia-mond particles (UDDs) of 5-10 nm size, which are readily prepared in form of a monolayer beneath a TiO 2 sol-gel thin film on silicon substrate surfaces. [13] Being able to also initiate diamond nucleation, UDDs have become a commonly used tool for CDV diamond seeding and initiation of CVD growth. [14] UDD seeding does not require additional diamond nucleation since the diamond film can grow epitaxially on the UDD grains during the CVD process, which leads to ultra-thin films (30-50 nm) with full surface coverage. In this work NCD film nucleation and growth were studied using UDD particles that are buried under a sol-gel TiO 2 layer spin-coated on UDD seeded silicon substrates. It was observed that when immersed in a conventional H 2 /CH 4 microwave plasma that is commonly used for CVD diamond growth, a partial dissolution of the UDD grain into the TiO 2 occurs. Subsequent carbon diffusion through the 5-10 nm thick TiO 2 layer leads to growth and transformation of the carbon atoms into sp 3 bonded clusters, i.e., diamond nuclei. This was studied by high-resolution transmission electron microscopy (HRTEM), energy-filtered TEM (EFTEM), and electron energy loss spectroscopy (EELS). In order to elucidate the diamond nucleation process in more detail, different sample preparation methods were used, three of which are depicted schematically in the insets of Figure 1 and Figure 2. The first method (method I), inset in Figure 1a, shows a TiO 2 layer deposited by sol-gel technique on a bare Si substrate. The TiO 2 precursor solution contained UDD particles in the mixture. Due to the very low thickness of the TiO 2 interlayer, the time that is required for carbon-saturation of the layer should be very short when exposed to the H 2 /CH 4 plasma during the microwave plasma enhanced CVD (MW PE CVD) process. [15,16] However, due to a too low UDD concentration used in the TiO 2 precursor solution and problems experienced with the homogenous dispersion of the powder, no homogeneous film was obtained after 60 min of CVD growth. Specifically, it appears that the UDD particles float on top of the precursor material and cluster together. This leads, after the formation of the sol-gel layer, to the creation of areas with no seeds and areas with seeds floating on top, as shown in Figure 1a. Furthermore, Figure 1b indicates that no diamond film growth takes place where no diamond seeds were present prior to the growth process, hence proving the necessity of UDD pre-treatment of the TiO 2 coated Si substrates. If no UDD seeding is used at all (method II), i.e., bare TiO 2 is deposited directly onto the Si substrate as shown schematically in COMMUNICATION www.advmat.de', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'B-NCD-layer', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'PID-control', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'temperature regulator', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Otta-Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'journal_article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '673', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/pssa.201000291', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Pdf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.eissn', + value: '1521-4095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'Advanced Materials', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Open Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'With Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; ZHANG, Xiaowang; Erni, Rolf; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, Gustaaf (182400) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:11:05.541+0000', + type: 'item' + } + }, + '9beaeeab-101c-4ad7-9045-ec72ca58a03b': { + submitterDecision: null, + workflowDecision: null, + adminDecision: null, + submitterNote: null, + workflowNote: null, + matchObject: { + id: '9beaeeab-101c-4ad7-9045-ec72ca58a03b', + uuid: '9beaeeab-101c-4ad7-9045-ec72ca58a03b', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + handle: null, + metadata: [ { + key: 'dc.contributor.author', + value: 'DAENEN, Marc', + language: null, + authority: 'rp02327', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Zhang, L', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'Erni, R', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Bael, MK', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Pawel', + language: null, + authority: 'rp00802', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, G', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '2009', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '0935-9648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Ennea-Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'Journal/Magazine Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '+', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/adma.200802305', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Web of Science', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'ADVANCED MATERIALS', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Closed Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'No Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Marc; Zhang, L; Erni, R; WILLIAMS, Oliver; HARDY, An; Van Bael, MK; WAGNER, Pawel; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, G (2009) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2 Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + } ], + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T08:50:25.318+0000', + type: 'item' + } + } +}; + +export const mockSubmissionState: SubmissionObjectState = Object.assign({}, { + 826: { + collection: mockSubmissionCollectionId, + definition: 'traditional', + selfUrl: mockSubmissionSelfUrl, + activeSection: null, + sections: { + extraction: { + config: '', + mandatory: true, + sectionType: 'utils', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'collection': { + config: '', + mandatory: true, + sectionType: 'collection', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'traditionalpageone': { + header: 'submit.progressbar.describe.stepone', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpageone', + mandatory: true, + sectionType: 'submission-form', + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + formId: '2_traditionalpageone', + isLoading: false, + isValid: false, + removePending: false + } as any, + 'traditionalpagetwo': { + header: 'submit.progressbar.describe.steptwo', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpagetwo', + mandatory: false, + sectionType: 'submission-form', + collapsed: false, + enabled: false, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'detect-duplicate': { + header: 'submit.progressbar.detect-duplicate', + config: '', + mandatory: true, + sectionType: 'detect-duplicate', + collapsed: false, + enabled: true, + data: { + matches: {} + }, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'upload': { + header: 'submit.progressbar.upload', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionuploads/upload', + mandatory: true, + sectionType: 'upload', + collapsed: false, + enabled: true, + data: { + files: [] + }, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'license': { + header: 'submit.progressbar.license', + config: '', + mandatory: true, + sectionType: 'license', + visibility: { + main: null, + other: 'READONLY' + }, + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any + }, + isLoading: false, + savePending: false, + depositPending: false + } +}); + +export const mockSubmissionStateWithDuplicate: SubmissionObjectState = Object.assign({}, { + 826: { + collection: mockSubmissionCollectionId, + definition: 'traditional', + selfUrl: mockSubmissionSelfUrl, + activeSection: null, + sections: { + 'extraction': { + config: '', + mandatory: true, + sectionType: 'utils', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false + } as any, + collection: { + config: '', + mandatory: true, + sectionType: 'collection', + visibility: { + main: 'HIDDEN', + other: 'HIDDEN' + }, + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false + } as any, + traditionalpageone: { + header: 'submit.progressbar.describe.stepone', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpageone', + mandatory: true, + sectionType: 'submission-form', + collapsed: false, + enabled: true, + data: {}, + errorsToShow: [], + formId: '2_traditionalpageone', + isLoading: false, + isValid: false + } as any, + traditionalpagetwo: { + header: 'submit.progressbar.describe.steptwo', + config: 'https://rest.api/dspace-spring-rest/api/config/submissionforms/traditionalpagetwo', + mandatory: false, + sectionType: 'submission-form', + collapsed: false, + enabled: false, + data: {}, + errorsToShow: [], + isLoading: false, + isValid: false + } as any, + 'detect-duplicate': { + header: 'submit.progressbar.detect-duplicate', + config: '', + mandatory: true, + sectionType: 'detect-duplicate', + collapsed: false, + enabled: true, + data: { + matches: mockDeduplicationMatches + }, + errorsToShow: [], + isLoading: false, + isValid: false, + removePending: false + } as any, + 'upload': { header: 'submit.progressbar.upload', config: 'https://rest.api/dspace-spring-rest/api/config/submissionuploads/upload', mandatory: true, @@ -1814,3 +3649,7 @@ export const mockAccessesFormData = { ] }; +// mockDeduplicationMatches id for Workflow decision +export const mockDeduplicationWorkflowId = '78ca1d06-cce7-4ee9-abda-46440d9b0bb7'; +// mockDeduplicationMatches id for Submitter decision +export const mockDeduplicationSubmitterId = 'ebae3c99-f438-4b65-879b-1eea7a9e0324'; diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts index 6b40678dedc..3bd9747749b 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts @@ -6,6 +6,7 @@ import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { SearchResult } from '../../../search/models/search-result.model'; +import { DuplicateMatchMetadataDetailConfig } from '../../../../submission/sections/detect-duplicate/models/duplicate-detail-metadata.model'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; @@ -40,6 +41,11 @@ export class ItemListPreviewComponent implements OnInit { */ @Input() showSubmitter = false; + /** + * An object representing the duplicate match + */ + @Input() metadataList: DuplicateMatchMetadataDetailConfig[] = []; + /** * Display thumbnails if required by configuration */ diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 777ad03c1da..7ad0e3e6263 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -244,6 +244,15 @@ import { import { ThemedCollectionDropdownComponent } from './collection-dropdown/themed-collection-dropdown.component'; import { MetadataFieldWrapperComponent } from './metadata-field-wrapper/metadata-field-wrapper.component'; import { LogInExternalProviderComponent } from './log-in/methods/log-in-external-provider/log-in-external-provider.component'; +import { + ThemedItemListPreviewComponent +} from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { + ItemListPreviewComponent +} from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; +import { + MyDSpaceItemStatusComponent +} from './object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; const MODULES = [ @@ -346,6 +355,9 @@ const COMPONENTS = [ ListableNotificationObjectComponent, DsoPageEditButtonComponent, MetadataFieldWrapperComponent, +// ItemListPreviewComponent, +// ThemedItemListPreviewComponent, + //MyDSpaceItemStatusComponent, ]; const ENTRY_COMPONENTS = [ diff --git a/src/app/shared/testing/sections-service.stub.ts b/src/app/shared/testing/sections-service.stub.ts index b687c512c21..5ec4da42601 100644 --- a/src/app/shared/testing/sections-service.stub.ts +++ b/src/app/shared/testing/sections-service.stub.ts @@ -17,6 +17,7 @@ export class SectionsServiceStub { updateSectionData = jasmine.createSpy('updateSectionData'); setSectionError = jasmine.createSpy('setSectionError'); setSectionStatus = jasmine.createSpy('setSectionStatus'); + isSectionActive = jasmine.createSpy('isSectionActive'); computeSectionConfiguredMetadata = jasmine.createSpy('computeSectionConfiguredMetadata'); getShownSectionErrors = jasmine.createSpy('getShownSectionErrors'); getSectionServerErrors = jasmine.createSpy('getSectionServerErrors'); diff --git a/src/app/submission/objects/submission-objects.actions.ts b/src/app/submission/objects/submission-objects.actions.ts index 9182611e479..b876b72b10a 100644 --- a/src/app/submission/objects/submission-objects.actions.ts +++ b/src/app/submission/objects/submission-objects.actions.ts @@ -55,6 +55,9 @@ export const SubmissionObjectActionTypes = { DISCARD_SUBMISSION: type('dspace/submission/DISCARD_SUBMISSION'), DISCARD_SUBMISSION_SUCCESS: type('dspace/submission/DISCARD_SUBMISSION_SUCCESS'), DISCARD_SUBMISSION_ERROR: type('dspace/submission/DISCARD_SUBMISSION_ERROR'), + SET_DUPLICATE_DECISION: type('dspace/submission/SET_DUPLICATE_DECISION'), + SET_DUPLICATE_DECISION_SUCCESS: type('dspace/submission/SET_DUPLICATE_DECISION_SUCCESS'), + SET_DUPLICATE_DECISION_ERROR: type('dspace/submission/SET_DUPLICATE_DECISION_ERROR'), // Upload file types NEW_FILE: type('dspace/submission/NEW_FILE'), @@ -65,6 +68,9 @@ export const SubmissionObjectActionTypes = { ADD_SECTION_ERROR: type('dspace/submission/ADD_SECTION_ERROR'), DELETE_SECTION_ERROR: type('dspace/submission/DELETE_SECTION_ERROR'), REMOVE_SECTION_ERRORS: type('dspace/submission/REMOVE_SECTION_ERRORS'), + + // Clean detect duplicate section + CLEAN_DETECT_DUPLICATE: type('dspace/submission/CLEAN_DETECT_DUPLICATE') }; @@ -202,6 +208,25 @@ export class DisableSectionAction implements Action { } } +/** + * Removes data and makes 'detect-duplicate' section not visible. + */ +export class CleanDetectDuplicateAction implements Action { + type = SubmissionObjectActionTypes.CLEAN_DETECT_DUPLICATE; + payload: { + submissionId: string; + }; + + /** + * creates a new CleanDetectDuplicateAction + * + * @param submissionId Id of the submission on which perform the action + */ + constructor(submissionId: string ) { + this.payload = { submissionId }; + } +} + export class UpdateSectionDataAction implements Action { type = SubmissionObjectActionTypes.UPDATE_SECTION_DATA; payload: { @@ -809,6 +834,66 @@ export class DeleteUploadedFileAction implements Action { } } +export class SetDuplicateDecisionAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION; + payload: { + submissionId: string; + sectionId: string; + }; + + /** + * Create a new SetDuplicateDecisionAction + * + * @param submissionId + * the submission's ID + * @param sectionId + * the section's ID + */ + constructor(submissionId: string, sectionId: string) { + this.payload = { submissionId, sectionId }; + } +} + +export class SetDuplicateDecisionSuccessAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS; + payload: { + submissionId: string; + sectionId: string; + submissionObject: SubmissionObject[]; + }; + + /** + * Create a new SetDuplicateDecisionSuccessAction + * + * @param submissionId + * the submission's ID + * @param sectionId + * the section's ID + * @param submissionObject + * the submission's Object + */ + constructor(submissionId: string, sectionId: string, submissionObject: SubmissionObject[]) { + this.payload = { submissionId, sectionId, submissionObject }; + } +} + +export class SetDuplicateDecisionErrorAction implements Action { + type = SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_ERROR; + payload: { + submissionId: string; + }; + + /** + * Create a new SetDuplicateDecisionErrorAction + * + * @param submissionId + * the submission's ID + */ + constructor(submissionId: string) { + this.payload = { submissionId }; + } +} + /** * Export a type alias of all actions in this action group @@ -847,4 +932,7 @@ export type SubmissionObjectAction = DisableSectionAction | SaveSubmissionSectionFormAction | SaveSubmissionSectionFormSuccessAction | SaveSubmissionSectionFormErrorAction - | SetActiveSectionAction; + | SetActiveSectionAction + | SetDuplicateDecisionAction + | SetDuplicateDecisionSuccessAction + | SetDuplicateDecisionErrorAction; diff --git a/src/app/submission/objects/submission-objects.effects.spec.ts b/src/app/submission/objects/submission-objects.effects.spec.ts index a1bb878aa59..0df4b62431a 100644 --- a/src/app/submission/objects/submission-objects.effects.spec.ts +++ b/src/app/submission/objects/submission-objects.effects.spec.ts @@ -8,6 +8,7 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { SubmissionObjectEffects } from './submission-objects.effects'; import { + CleanDetectDuplicateAction, CompleteInitSubmissionFormAction, DepositSubmissionAction, DepositSubmissionErrorAction, @@ -21,6 +22,8 @@ import { SaveSubmissionFormSuccessAction, SaveSubmissionSectionFormErrorAction, SaveSubmissionSectionFormSuccessAction, + SetDuplicateDecisionErrorAction, + SetDuplicateDecisionSuccessAction, SubmissionObjectActionTypes, UpdateSectionDataAction } from './submission-objects.actions'; @@ -35,7 +38,8 @@ import { mockSubmissionId, mockSubmissionRestResponse, mockSubmissionSelfUrl, - mockSubmissionState + mockSubmissionState, + mockSubmissionStateWithDuplicate } from '../../shared/mocks/submission.mock'; import { SubmissionSectionModel } from '../../core/config/models/config-submission-section.model'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; @@ -605,6 +609,57 @@ describe('SubmissionObjectEffects test suite', () => { expect(submissionServiceStub.notifyNewSection).toHaveBeenCalled(); }); + it('should send CLEAN_DETECT_DUPLICATE when duplicate section is removed', () => { + store.nextState({ + submission: { + objects: mockSubmissionStateWithDuplicate + } + } as any); + + const response = [Object.assign({}, mockSubmissionRestResponse[0], { + sections: mockSectionsData + })]; + actions = hot('--a-', { + a: { + type: SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS, + payload: { + submissionId: submissionId, + submissionObject: response + } + } + }); + + const expected = cold('--(bcde)-', { + b: new UpdateSectionDataAction( + submissionId, + 'traditionalpageone', + mockSectionsData.traditionalpageone as any, + [], + [] + ), + c: new UpdateSectionDataAction( + submissionId, + 'license', + mockSectionsData.license as any, + [], + [] + ), + d: new UpdateSectionDataAction( + submissionId, + 'upload', + mockSectionsData.upload as any, + [], + [] + ), + e: new CleanDetectDuplicateAction( + submissionId + ), + }); + + expect(submissionObjectEffects.saveSubmissionSuccess$).toBeObservable(expected); + // expect(notificationsServiceStub.success).toHaveBeenCalled(); + }); + }); describe('saveSubmissionSectionSuccess$', () => { @@ -1018,6 +1073,71 @@ describe('SubmissionObjectEffects test suite', () => { }); }); + describe('saveDuplicateDecision$', () => { + it('should return a SET_DUPLICATE_DECISION action on success', () => { + actions = hot('--a-', { + a: { + type: SubmissionObjectActionTypes.SET_DUPLICATE_DECISION, + payload: { + submissionId: submissionId, + sectionId: 'detect-duplicate' + } + } + }); + + submissionJsonPatchOperationsServiceStub.jsonPatchByResourceID.and.returnValue(observableOf(mockSubmissionRestResponse)); + const expected = cold('--b-', { + b: new SetDuplicateDecisionSuccessAction( + submissionId, + 'detect-duplicate', + mockSubmissionRestResponse as any, + ) + }); + + expect(submissionObjectEffects.saveDuplicateDecision$).toBeObservable(expected); + }); + + it('should return a SET_DUPLICATE_DECISION_ERROR action on error', () => { + actions = hot('--a-', { + a: { + type: SubmissionObjectActionTypes.SET_DUPLICATE_DECISION, + payload: { + submissionId: submissionId, + sectionId: 'detect-duplicate' + } + } + }); + + submissionJsonPatchOperationsServiceStub.jsonPatchByResourceID.and.callFake( + () => observableThrowError('Error') + ); + const expected = cold('--b-', { + b: new SetDuplicateDecisionErrorAction( + submissionId + ) + }); + + expect(submissionObjectEffects.saveDuplicateDecision$).toBeObservable(expected); + }); + }); + + describe('setDuplicateDecisionSuccess$', () => { + it('should display a dedup success notification', () => { + actions = hot('--a-', { + a: { + type: SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS, + payload: { + submissionId: submissionId + } + } + }); + + submissionObjectEffects.setDuplicateDecisionSuccess$.subscribe(() => { + expect(notificationsServiceStub.success).toHaveBeenCalled(); + }); + }); + }); + describe('depositSubmissionSuccess$', () => { it('should display a new success notification and redirect to mydspace', () => { actions = hot('--a-', { diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index 98646009d5b..24b76cf8690 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -14,7 +14,7 @@ import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/w import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; import { SubmissionJsonPatchOperationsService } from '../../core/submission/submission-json-patch-operations.service'; -import { isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; +import { isEmpty, isNotEmpty, isNotUndefined, isUndefined } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { SectionsType } from '../sections/sections-type'; import { SectionsService } from '../sections/sections.service'; @@ -22,6 +22,7 @@ import { SubmissionState } from '../submission.reducers'; import { SubmissionService } from '../submission.service'; import parseSectionErrors from '../utils/parseSectionErrors'; import { + CleanDetectDuplicateAction, CompleteInitSubmissionFormAction, DepositSubmissionAction, DepositSubmissionErrorAction, @@ -40,6 +41,9 @@ import { SaveSubmissionSectionFormAction, SaveSubmissionSectionFormErrorAction, SaveSubmissionSectionFormSuccessAction, + SetDuplicateDecisionAction, + SetDuplicateDecisionErrorAction, + SetDuplicateDecisionSuccessAction, SubmissionObjectAction, SubmissionObjectActionTypes, UpdateSectionDataAction, @@ -55,6 +59,9 @@ import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionE import { FormState } from '../../shared/form/form.reducer'; import { SubmissionSectionObject } from './submission-section-object.model'; import { SubmissionSectionError } from './submission-section-error.model'; +import { + WorkspaceitemSectionDetectDuplicateObject +} from '../../core/submission/models/workspaceitem-section-deduplication.model'; @Injectable() export class SubmissionObjectEffects { @@ -232,6 +239,29 @@ export class SubmissionObjectEffects { catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); }))); + saveDuplicateDecision$ = createEffect(() => this.actions$.pipe( + ofType(SubmissionObjectActionTypes.SET_DUPLICATE_DECISION), + switchMap((action: SetDuplicateDecisionAction) => { + return this.operationsService.jsonPatchByResourceID( + this.submissionService.getSubmissionObjectLinkName(), + action.payload.submissionId, + 'sections', + action.payload.sectionId).pipe( + map((response: SubmissionObject[]) => new SetDuplicateDecisionSuccessAction( + action.payload.submissionId, + action.payload.sectionId, + response) + ), + catchError(() => observableOf(new SetDuplicateDecisionErrorAction(action.payload.submissionId)))); + })) + ); + + setDuplicateDecisionSuccess$ = createEffect(() => this.actions$.pipe( + ofType(SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS), + tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.detect-duplicate.decision-success-notice')))), + { dispatch: false } + ); + /** * Dispatch a [DepositSubmissionSuccessAction] or a [DepositSubmissionErrorAction] on error */ @@ -434,6 +464,10 @@ export class SubmissionObjectEffects { && isEmpty(sections[sherpaPoliciesSectionId])) { mappedActions.push(new UpdateSectionDataAction(submissionId, sherpaPoliciesSectionId, null, [], [])); } + if (isNotEmpty((currentState.sections['detect-duplicate']?.data as WorkspaceitemSectionDetectDuplicateObject)?.matches) + && isUndefined(sections['detect-duplicate'])) { + mappedActions.push(new CleanDetectDuplicateAction(submissionId)); + } }); } diff --git a/src/app/submission/objects/submission-objects.reducer.spec.ts b/src/app/submission/objects/submission-objects.reducer.spec.ts index 2a24afae19c..cc3b828d750 100644 --- a/src/app/submission/objects/submission-objects.reducer.spec.ts +++ b/src/app/submission/objects/submission-objects.reducer.spec.ts @@ -29,14 +29,18 @@ import { SaveSubmissionSectionFormErrorAction, SaveSubmissionSectionFormSuccessAction, SectionStatusChangeAction, + SetDuplicateDecisionAction, + SetDuplicateDecisionSuccessAction, SubmissionObjectAction, UpdateSectionDataAction } from './submission-objects.actions'; import { SectionsType } from '../sections/sections-type'; import { + mockDeduplicationMatches, mockSubmissionCollectionId, mockSubmissionDefinitionResponse, mockSubmissionId, + mockSubmissionObject, mockSubmissionSelfUrl, mockSubmissionState } from '../../shared/mocks/submission.mock'; @@ -65,6 +69,7 @@ describe('submissionReducer test suite', () => { sections: Object.create(null), isLoading: true, savePending: false, + saveDecisionPending: false, depositPending: false, } }; @@ -644,4 +649,62 @@ describe('submissionReducer test suite', () => { expect(newState[826].sections.upload.data).toEqual(expectedState); }); + it('should set the decision flag to true', () => { + const state: SubmissionObjectState = Object.assign({}, initState, { + [submissionId]: Object.assign({}, initState[submissionId], { + saveDecisionPending: true + }) + }); + + const action = new SetDuplicateDecisionAction(submissionId, 'detect-duplicate'); + const newState = submissionObjectReducer(state, action); + + expect(newState[826].saveDecisionPending).toBeTruthy(); + }); + + it('should set the duplicate decision', () => { + const state: SubmissionObjectState = Object.assign({}, initState, { + [submissionId]: Object.assign({}, initState[submissionId], { + sections: Object.assign({}, initState[submissionId].sections, { + 'detect-duplicate': Object.assign({}, initState[submissionId].sections['detect-duplicate'], { + enabled: true, + data: { + matches: mockDeduplicationMatches + } + }) + }) + }) + }); + const submissionObject = { + [submissionId]: { + id: parseInt(submissionId, 10), + sections: { + 'detect-duplicate': { + matches: mockDeduplicationMatches + } + } + } + }; + + const action = new SetDuplicateDecisionSuccessAction(submissionId, 'detect-duplicate', submissionObject as any); + const newState = submissionObjectReducer(state, action); + + expect(newState[826].sections['detect-duplicate'].enabled).toBeTrue(); + expect(newState[826].sections['detect-duplicate'].data).toEqual({ matches: mockDeduplicationMatches } as any); + expect(newState[826].saveDecisionPending).toBeFalse(); + }); + + it('should set the decision flag to false', () => { + const state: SubmissionObjectState = Object.assign({}, initState, { + [submissionId]: Object.assign({}, initState[submissionId], { + saveDecisionPending: false + }) + }); + + const action = new SetDuplicateDecisionSuccessAction(submissionId, 'detect-duplicate', [mockSubmissionObject as any]); + const newState = submissionObjectReducer(state, action); + + expect(newState[826].saveDecisionPending).toBeFalsy(); + }); + }); diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index a05bf05f52c..719b38132b9 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -6,6 +6,7 @@ import uniqWith from 'lodash/uniqWith'; import { ChangeSubmissionCollectionAction, + CleanDetectDuplicateAction, CompleteInitSubmissionFormAction, DeleteSectionErrorsAction, DeleteUploadedFileAction, @@ -33,6 +34,9 @@ import { SaveSubmissionSectionFormSuccessAction, SectionStatusChangeAction, SetActiveSectionAction, + SetDuplicateDecisionAction, + SetDuplicateDecisionErrorAction, + SetDuplicateDecisionSuccessAction, SetSectionFormId, SubmissionObjectAction, SubmissionObjectActionTypes, @@ -40,6 +44,9 @@ import { } from './submission-objects.actions'; import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model'; import { SubmissionSectionObject } from './submission-section-object.model'; +import { + WorkspaceitemSectionDetectDuplicateObject +} from '../../core/submission/models/workspaceitem-section-deduplication.model'; /** * An interface to represent SubmissionSectionObject entry @@ -87,6 +94,11 @@ export interface SubmissionObjectEntry { */ savePending?: boolean; + /** + * A boolean representing if a duplicate decision is pending + */ + saveDecisionPending?: boolean; + /** * A boolean representing if a submission deposit operation is pending */ @@ -224,6 +236,23 @@ export function submissionObjectReducer(state = initialState, action: Submission return removeSectionErrors(state, action as RemoveSectionErrorsAction); } + // detect duplicate + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION: { + return startSaveDecision(state, action as SetDuplicateDecisionAction); + } + + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_SUCCESS: { + return setDuplicateMatches(state, action as SetDuplicateDecisionSuccessAction); + } + + case SubmissionObjectActionTypes.SET_DUPLICATE_DECISION_ERROR: { + return endSaveDecision(state, action as SetDuplicateDecisionErrorAction); + } + + case SubmissionObjectActionTypes.CLEAN_DETECT_DUPLICATE: { + return cleanDetectDuplicateSection(state, action as CleanDetectDuplicateAction); + } + default: { return state; } @@ -329,6 +358,7 @@ function initSubmission(state: SubmissionObjectState, action: InitSubmissionForm sections: Object.create(null), isLoading: true, savePending: false, + saveDecisionPending: false, depositPending: false, }; return newState; @@ -811,3 +841,92 @@ function deleteFile(state: SubmissionObjectState, action: DeleteUploadedFileActi } return state; } + +// ------ Detect duplicate functions ------ // + +/** + * Set decision flag to true + * + * @param state + * the current state + * @param action + * a SetDuplicateDecisionAction + * @return SubmissionObjectState + * the new state, with the decision flag changed. + */ +function startSaveDecision(state: SubmissionObjectState, action: SetDuplicateDecisionAction): SubmissionObjectState { + if (hasValue(state[ action.payload.submissionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + saveDecisionPending: true, + }) + }); + } else { + return state; + } +} + +function setDuplicateMatches(state: SubmissionObjectState, action: SetDuplicateDecisionSuccessAction) { + const index: any = findKey( + action.payload.submissionObject, + {id: parseInt(action.payload.submissionId, 10) as any}); + const sectionData = action.payload.submissionObject[index].sections[ action.payload.sectionId ] as WorkspaceitemSectionDetectDuplicateObject; + const newData = (sectionData && sectionData.matches) ? sectionData : Object.create({}); + + if (hasValue(state[ action.payload.submissionId ].sections[ action.payload.sectionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + sections: Object.assign({}, state[ action.payload.submissionId ].sections, + Object.assign({}, { + [ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections [ action.payload.sectionId ], { + enabled: true, + data: newData + }) + }) + ), + saveDecisionPending: false + }) + }); + } else { + return state; + } +} + +/** + * Set decision flag to false + * + * @param state + * the current state + * @param action + * a SetDuplicateDecisionSuccessAction or SetDuplicateDecisionErrorAction + * @return SubmissionObjectState + * the new state, with the decision flag changed. + */ +function endSaveDecision(state: SubmissionObjectState, action: SetDuplicateDecisionSuccessAction | SetDuplicateDecisionErrorAction): SubmissionObjectState { + if (hasValue(state[ action.payload.submissionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + saveDecisionPending: false, + }) + }); + } else { + return state; + } +} + +function cleanDetectDuplicateSection(state: SubmissionObjectState, action: CleanDetectDuplicateAction): SubmissionObjectState { + if (isNotEmpty(state[ action.payload.submissionId ])) { + return Object.assign({}, state, { + [ action.payload.submissionId ]: Object.assign({}, state[ action.payload.submissionId ], { + sections: Object.assign({}, state[ action.payload.submissionId ].sections, { + [ 'detect-duplicate' ]: Object.assign({}, state[ action.payload.submissionId ].sections [ 'detect-duplicate' ], { + enabled: false, + data: {} + }) + }) + }) + }); + } else { + return state; + } +} diff --git a/src/app/submission/sections/detect-duplicate/detect-duplicate.service.spec.ts b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.spec.ts new file mode 100644 index 00000000000..12080dcbdeb --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.spec.ts @@ -0,0 +1,162 @@ +import { Store, StoreModule } from '@ngrx/store'; +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { cold } from 'jasmine-marbles'; + +import { DetectDuplicateService } from './detect-duplicate.service'; +import { submissionReducers } from '../../submission.reducers'; +import { + mockDeduplicationMatches, + mockDeduplicationSubmitterId, + mockDeduplicationWorkflowId, + mockSubmissionId, + mockSubmissionState, mockSubmissionStateWithDuplicate +} from '../../../shared/mocks/submission.mock'; +import { SetDuplicateDecisionAction } from '../../objects/submission-objects.actions'; + +describe('DetectDuplicateService', () => { + let service: DetectDuplicateService; + let serviceAsAny: any; + let store: any; + let initialState: any; + const sectionId = 'detect-duplicate'; + + function init() { + initialState = { submission: { objects: mockSubmissionState } }; + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({ submission: submissionReducers } as any), + ], + providers: [ + provideMockStore({ initialState }), + { provide: DetectDuplicateService, useValue: service } + ] + }).compileComponents(); + })); + + beforeEach(() => { + store = TestBed.inject(Store); + service = new DetectDuplicateService(store); + serviceAsAny = service; + spyOn(store, 'dispatch'); + }); + + describe('Testing methods with empty deduplication matches', () => { + + describe('getDuplicateMatches', () => { + it('Should return an empty object', () => { + const result = service.getDuplicateMatches(mockSubmissionId, sectionId); + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: {} }, + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('getDuplicateMatchesByScope', () => { + it('Should return an empty object using isWorkFlow as true', () => { + const result = service.getDuplicateMatchesByScope(mockSubmissionId, sectionId, true); + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: {} }, + }); + expect(result).toBeObservable(expected); + }); + it('Should return an empty object using isWorkFlow as false', () => { + const result = service.getDuplicateMatchesByScope(mockSubmissionId, sectionId, false); + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: {} }, + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('getDuplicateTotalMatches', () => { + it('Should return zero (0)', () => { + const result = service.getDuplicateTotalMatches(mockSubmissionId, sectionId); + const expected = cold('(a)', { + a: 0, + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('saveDuplicateDecision', () => { + it('Should call store.dispatch', () => { + const action = new SetDuplicateDecisionAction(mockSubmissionId, sectionId); + service.saveDuplicateDecision(mockSubmissionId, sectionId); + + expect(serviceAsAny.store.dispatch).toHaveBeenCalledWith(action); + }); + }); + }); + + describe('Testing methods with deduplication matches', () => { + + beforeEach(() => { + initialState = { submission: { objects: mockSubmissionStateWithDuplicate } }; + store.setState(initialState); + }); + + describe('getDuplicateMatches', () => { + it('Should return an object with items', () => { + const result = service.getDuplicateMatches(mockSubmissionId, sectionId); + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: mockDeduplicationMatches }, + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('getDuplicateMatchesByScope', () => { + it('Should return an object with workflow items only, using isWorkFlow as true', () => { + const result = service.getDuplicateMatchesByScope(mockSubmissionId, sectionId, true); + const mockDeduplicationWorkflowMatches = Object.assign({}, mockDeduplicationMatches); + delete mockDeduplicationWorkflowMatches[mockDeduplicationWorkflowId]; + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: mockDeduplicationWorkflowMatches }, + }); + expect(result).toBeObservable(expected); + }); + it('Should return an object with submitter items only, using isWorkFlow as false', () => { + const result = service.getDuplicateMatchesByScope(mockSubmissionId, sectionId, false); + const mockDeduplicationSubmitterMatches = Object.assign({}, mockDeduplicationMatches); + delete mockDeduplicationSubmitterMatches[mockDeduplicationSubmitterId]; + const expected = cold('(ab)', { + a: { matches: {} }, + b: { matches: mockDeduplicationSubmitterMatches }, + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('getDuplicateTotalMatches', () => { + it('Should return the number of the items', () => { + const result = service.getDuplicateTotalMatches(mockSubmissionId, sectionId); + const expected = cold('(ab)', { + a: 0, + b: Object.keys(mockDeduplicationMatches).length + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('saveDuplicateDecision', () => { + it('Should call store.dispatch', () => { + const action = new SetDuplicateDecisionAction(mockSubmissionId, sectionId); + service.saveDuplicateDecision(mockSubmissionId, sectionId); + + expect(serviceAsAny.store.dispatch).toHaveBeenCalledWith(action); + }); + }); + }); + +}); diff --git a/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts new file mode 100644 index 00000000000..8c32296af57 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/detect-duplicate.service.ts @@ -0,0 +1,113 @@ +import { Injectable } from '@angular/core'; + +import { select, Store } from '@ngrx/store'; +import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; + +import { SubmissionState } from '../../submission.reducers'; +import {CleanDetectDuplicateAction, SetDuplicateDecisionAction} from '../../objects/submission-objects.actions'; +import { submissionSectionDataFromIdSelector } from '../../selectors'; +import { WorkspaceitemSectionDetectDuplicateObject } from '../../../core/submission/models/workspaceitem-section-deduplication.model'; +import {isEmpty, isNotEmpty, isNotUndefined} from '../../../shared/empty.util'; +import { Observable } from 'rxjs'; + +/** + * A service that provides methods used in the deduplication process. + */ +@Injectable() +export class DetectDuplicateService { + + /** + * Initialize service variables. + * @param {Store} store + */ + constructor(private store: Store) { + } + + /** + * Get the list of the possible duplications. + * @param {string} submissionId + * The submission id + * @param {string} sectionId + * The section id + * @return Observable + * Returns the list of the possible duplications + */ + getDuplicateMatches(submissionId: string, sectionId: string) { + return this.store.pipe( + select(submissionSectionDataFromIdSelector(submissionId, sectionId)), + map((sectionData: WorkspaceitemSectionDetectDuplicateObject) => { + let matches: {}; + if (isNotEmpty(sectionData)) { + matches = sectionData; + } + return matches; + }), + startWith({ matches: {} }), + distinctUntilChanged()); + } + + /** + * Get the list of the possible duplications for a specific submission scope. + * @param {string} submissionId + * The submission id + * @param {string} sectionId + * The section id + * @param {boolean} isWorkFlow + * If TRUE the submission scope is the 'workflow'; 'workspace' otherwise. + * @return Observable + * Returns the list of the possible duplicates + */ + getDuplicateMatchesByScope(submissionId: string, sectionId: string, isWorkFlow: boolean): Observable { + return this.getDuplicateMatches(submissionId, sectionId).pipe( + map((item: WorkspaceitemSectionDetectDuplicateObject) => { + const outputObject: WorkspaceitemSectionDetectDuplicateObject = {} as WorkspaceitemSectionDetectDuplicateObject; + outputObject.matches = {}; + if (isNotUndefined(item)) { + Object.keys(item.matches) + .filter((key) => { + let output = false; + if (isWorkFlow) { + output = isEmpty(item.matches[key].workflowDecision); + } else { + output = isEmpty(item.matches[key].submitterDecision); + } + return output; + }) + .forEach((key) => { + outputObject.matches[key] = item.matches[key]; + }); + } else { + // Item is undefined, we should clear / clean the whole section + this.store.dispatch(new CleanDetectDuplicateAction(submissionId)); + } + return outputObject; + }) + ); + } + + /** + * Get the count of the possible duplications. + * @param {string} submissionId + * The submission id + * @param {string} sectionId + * The section id + * @return Observable + * Returns the number of the possible duplications + */ + getDuplicateTotalMatches(submissionId: string, sectionId: string) { + return this.getDuplicateMatches(submissionId, sectionId).pipe( + map((sectionData: WorkspaceitemSectionDetectDuplicateObject) => Object.keys(sectionData.matches).length), + distinctUntilChanged()); + } + + /** + * Save the decision into the store. + * @param {string} submissionId + * The submission id + * @param {string} sectionId + * The section id + */ + saveDuplicateDecision(submissionId: string, sectionId: string): void { + this.store.dispatch(new SetDuplicateDecisionAction(submissionId, sectionId)); + } +} diff --git a/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.html b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.html new file mode 100644 index 00000000000..b3c4fc8423c --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.html @@ -0,0 +1,80 @@ + + +
+
+
+
+ + {{submitterDecision$ | async}} + +
+
+ + +
+
+
+ +
+ + + + +
+ +
+ +
+ + + + + + + diff --git a/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.spec.ts b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.spec.ts new file mode 100644 index 00000000000..06b362711ee --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.spec.ts @@ -0,0 +1,486 @@ +import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { CommonModule } from '@angular/common'; +import { FormBuilder } from '@angular/forms'; + +import { cold } from 'jasmine-marbles'; +import { of as observableOf } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { DuplicateMatchComponent } from './duplicate-match.component'; +import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub'; +import { SubmissionService } from '../../../submission.service'; +import { SectionsService } from '../../sections.service'; +import { SectionsServiceStub } from '../../../../shared/testing/sections-service.stub'; +import { DetectDuplicateService } from '../detect-duplicate.service'; +import { getMockDetectDuplicateService } from '../../../../shared/mocks/mock-detect-duplicate-service'; +import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; +import { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; +import { DetectDuplicateMatch } from '../../../../core/submission/models/workspaceitem-section-deduplication.model'; +import { DuplicateDecisionType } from '../models/duplicate-decision-type'; +import { DuplicateDecision } from '../models/duplicate-decision.model'; +import { createTestComponent } from '../../../../shared/testing/utils.test'; +import { provideMockStore } from '@ngrx/store/testing'; + +const metadata = [ + { + key: 'dc.contributor.author', + value: 'DAENEN, Michael', + language: null, + authority: 'rp02165', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'ZHANG, Xiaowang', + language: null, + authority: 'rp01733', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'Erni, Rolf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.contributor.author', + value: 'WILLIAMS, Oliver', + language: null, + authority: 'rp01028', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'HARDY, An', + language: null, + authority: 'rp02004', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'VAN BAEL, Marlies', + language: null, + authority: 'rp01865', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'WAGNER, Patrick', + language: null, + authority: 'rp00839', + confidence: 400 + }, { + key: 'dc.contributor.author', + value: 'HAENEN, Ken', + language: null, + authority: 'rp00770', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'NESLADEK, Milos', + language: null, + authority: 'rp00350', + confidence: 500 + }, { + key: 'dc.contributor.author', + value: 'Van Tendeloo, Gustaaf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.date.issued', + value: '182400', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '09359648', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issn', + value: '15214095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'The diamond nucleation step is critical for the chemical vapor deposition (CVD) of diamond on non-diamond substrates, i.e., for heteroepitaxial as well as polycrystalline growth on non-diamond (foreign) substrates. This process has been studied intensively over the past 20 years. [1-8] In general, diamond CVD growth on foreign substrates requires artificial formation of diamond nucleation sites on the substrate\'s surface. The high surface energy of diamond, [9] usually prevents direct, heterogeneous diamond nucleation from the gas phase, hence diamond growth cannot be initiated without this critical nucleation step. [5,8] As for the subsequently occurring diamond growth, it is assumed that atomic hydrogen is the only essential mediator required for stabilizing the diamond phase. Nonetheless, it was recently suggested that diamond can also grow in bulk, i.e., in a solid state environment, such as the sub-surface of silicon, if carbon atoms are sub-implanted by low energetic beams and transformed into diamond grains. [10,11] In this communication, we show that diffusion based transport of carbon atoms from diamond seeds through an interlayer is yet another mechanism by which diamond nuclei can be formed. This process opens further possibilities for the LPLTgrowth of synthetic diamond on a variety of substrates and gives access to new applications for nanocrystal-line diamonds (NCD), where diamond-like carbon and amorphous carbon are already applicable. [12] Carbon transport and subsequently occurring sp 3 bonded carbon cluster formation originates from dissolving so-called ultra-dispersed nanodia-mond particles (UDDs) of 5-10 nm size, which are readily prepared in form of a monolayer beneath a TiO 2 sol-gel thin film on silicon substrate surfaces. [13] Being able to also initiate diamond nucleation, UDDs have become a commonly used tool for CDV diamond seeding and initiation of CVD growth. [14] UDD seeding does not require additional diamond nucleation since the diamond film can grow epitaxially on the UDD grains during the CVD process, which leads to ultra-thin films (30-50 nm) with full surface coverage. In this work NCD film nucleation and growth were studied using UDD particles that are buried under a sol-gel TiO 2 layer spin-coated on UDD seeded silicon substrates. It was observed that when immersed in a conventional H 2 /CH 4 microwave plasma that is commonly used for CVD diamond growth, a partial dissolution of the UDD grain into the TiO 2 occurs. Subsequent carbon diffusion through the 5-10 nm thick TiO 2 layer leads to growth and transformation of the carbon atoms into sp 3 bonded clusters, i.e., diamond nuclei. This was studied by high-resolution transmission electron microscopy (HRTEM), energy-filtered TEM (EFTEM), and electron energy loss spectroscopy (EELS). In order to elucidate the diamond nucleation process in more detail, different sample preparation methods were used, three of which are depicted schematically in the insets of Figure 1 and Figure 2. The first method (method I), inset in Figure 1a, shows a TiO 2 layer deposited by sol-gel technique on a bare Si substrate. The TiO 2 precursor solution contained UDD particles in the mixture. Due to the very low thickness of the TiO 2 interlayer, the time that is required for carbon-saturation of the layer should be very short when exposed to the H 2 /CH 4 plasma during the microwave plasma enhanced CVD (MW PE CVD) process. [15,16] However, due to a too low UDD concentration used in the TiO 2 precursor solution and problems experienced with the homogenous dispersion of the powder, no homogeneous film was obtained after 60 min of CVD growth. Specifically, it appears that the UDD particles float on top of the precursor material and cluster together. This leads, after the formation of the sol-gel layer, to the creation of areas with no seeds and areas with seeds floating on top, as shown in Figure 1a. Furthermore, Figure 1b indicates that no diamond film growth takes place where no diamond seeds were present prior to the growth process, hence proving the necessity of UDD pre-treatment of the TiO 2 coated Si substrates. If no UDD seeding is used at all (method II), i.e., bare TiO 2 is deposited directly onto the Si substrate as shown schematically in COMMUNICATION www.advmat.de', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.description.abstract', + value: 'Diamond nucleation and growth can occur by diffusion of carbon from buried ultradispersed diamond seeds on a silicon substrate through a titanium oxide interlayer. This knowledge can improve nucleation and adhesion of thin diamond films on various substrates.', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.language.iso', + value: 'English', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.publisher', + value: 'WILEY-BLACKWELL', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'B-NCD-layer', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'PID-control', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.subject.other', + value: 'temperature regulator', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.title', + value: 'Diamond Mono-Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.type', + value: 'journal_article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.epage', + value: '673', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.issue', + value: '6', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.spage', + value: '670', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.volume', + value: '21', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.publisher.place', + value: 'COMMERCE PLACE, 350 MAIN ST, MALDEN 02148, MA USA', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.doi', + value: '10.1002/pssa.201000291', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.isi', + value: 'WOS:000263492000007', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.provider.type', + value: 'Pdf', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.identifier.eissn', + value: '1521-4095', + language: null, + authority: null, + confidence: -1 + }, { + key: 'dc.source.type', + value: 'Article', + language: null, + authority: null, + confidence: -1 + }, { + key: 'local.bibliographicCitation.jtitle', + value: 'Advanced Materials', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.accessRights', + value: 'Open Access', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fulltext', + value: 'With Fulltext', + language: null, + authority: null, + confidence: -1 + }, { + key: 'item.fullcitation', + value: 'DAENEN, Michael; ZHANG, Xiaowang; Erni, Rolf; WILLIAMS, Oliver; HARDY, An; VAN BAEL, Marlies; WAGNER, Patrick; HAENEN, Ken; NESLADEK, Milos & Van Tendeloo, Gustaaf (182400) Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites.', + language: null, + authority: null, + confidence: -1 + }]; +const matchWorkflowMock: DetectDuplicateMatch = { + submitterDecision: null, + workflowDecision: 'reject', + adminDecision: null, + submitterNote: null, + workflowNote: 'dummy-note', + matchObject: { + id: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + uuid: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: metadata, + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:16:02.300+0000', + type: 'item' + } +} as any; +const matchWorkspaceMock: DetectDuplicateMatch = { + submitterDecision: 'reject', + workflowDecision: null, + adminDecision: null, + submitterNote: 'dummy-note', + workflowNote: null, + matchObject: { + id: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + uuid: '78ca1d06-cce7-4ee9-abda-46440d9b0bb7', + name: 'Diamond Nucleation by Carbon Transport from Buried Nanodiamond TiO2Sol-Gel Composites', + handle: null, + metadata: metadata, + inArchive: false, + discoverable: true, + withdrawn: false, + lastModified: '2019-11-21T16:16:02.300+0000', + type: 'item' + } +} as any; + +describe('DuplicateMatchComponent test suite', () => { + let comp: DuplicateMatchComponent; + let compAsAny: any; + let fixture: ComponentFixture; + let de: DebugElement; + let submissionServiceStub: any; + let modalService: any; + let formBuilder: FormBuilder; + let operationsBuilder: JsonPatchOperationsBuilder; + const initialState = {}; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserModule, + CommonModule, + TranslateModule.forRoot(), + ], + declarations: [ + DuplicateMatchComponent, + TestComponent, + ], + providers: [ + { provide: DetectDuplicateService, useClass: getMockDetectDuplicateService }, + FormBuilder, + NgbModal, + JsonPatchOperationsBuilder, + { provide: SectionsService, useClass: SectionsServiceStub }, + { provide: SubmissionService, useClass: SubmissionServiceStub }, + provideMockStore({ initialState }), + DuplicateMatchComponent, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + // First test to check the correct component creation + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('Should create DuplicateMatchComponent', inject([DuplicateMatchComponent], (app: DuplicateMatchComponent) => { + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(DuplicateMatchComponent); + comp = fixture.componentInstance; + compAsAny = comp; + submissionServiceStub = TestBed.inject(SubmissionService); + comp.sectionId = 'dummy-section-id'; + comp.itemId = 'dummy-item-id'; + comp.submissionId = 'dumy-submission-id'; + comp.index = 'dummy-index'; + modalService = TestBed.inject(NgbModal); + formBuilder = TestBed.inject(FormBuilder); + operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder); + compAsAny.sectionService.isSectionActive.and.returnValue(observableOf(true)); + spyOn(modalService, 'open').and.returnValue({ dismiss: () => true }); + spyOn(operationsBuilder, 'add'); + de = fixture.debugElement; + }); + + afterEach(() => { + comp = null; + compAsAny = null; + fixture.destroy(); + }); + + it('Should init section properly - with workflow', () => { + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkflowItem); + comp.match = matchWorkflowMock; + + fixture.detectChanges(); + const button = de.query(By.css('.btn')); + expect(button).not.toBeNull(); + expect(comp.decisionType).toEqual(DuplicateDecisionType.WORKFLOW); + expect(comp.hasDecision).toBeTrue(); + expect(comp.submitterNote).toBeUndefined(); + expect(comp.decisionLabelClass).toEqual('badge-light'); + }); + + it('Should init section properly - with workspace', () => { + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkspaceItem); + comp.match = matchWorkspaceMock; + + fixture.detectChanges(); + expect(comp.decisionType).toEqual(DuplicateDecisionType.WORKSPACE); + expect(comp.hasDecision).toBeTrue(); + expect(comp.submitterNote).toEqual(comp.match.submitterNote); + expect(comp.decisionLabelClass).toEqual('badge-success'); + }); + + describe('', () => { + beforeEach(() => { + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkspaceItem); + comp.match = matchWorkspaceMock; + spyOn(compAsAny, 'dispatchAction').and.callThrough(); + fixture.detectChanges(); + }); + + it('processingVerify should always an observable of true', () => { + comp.openModal({}); + comp.setAsDuplicate(); + expect(comp.processingVerify).toBeObservable(cold('(a|)', { + a: true + })); + }); + + it('modalRef.dismiss() should always be called', () => { + comp.openModal({}); + comp.setAsDuplicate(); + expect(comp.modalRef.dismiss()).toBeTrue(); + }); + + it('dispatchAction, in setAsDuplicate should be called with \'verify\', and \'WORKSPACE\'', () => { + // set FormControl values + comp.rejectForm.controls.reason.setValue('Dummy Reason'); + const decision = new DuplicateDecision( + 'verify', + DuplicateDecisionType.WORKSPACE, + 'Dummy Reason'); + comp.modalRef = modalService.open('ok'); + spyOn(comp.modalRef, 'dismiss'); + + comp.setAsDuplicate(); + + expect(compAsAny.dispatchAction).toHaveBeenCalledWith(decision); + expect(comp.modalRef.dismiss).toHaveBeenCalled(); + }); + + it('processingReject should always be an observable of true', () => { + comp.setAsNotDuplicate(); + expect(comp.processingReject).toBeObservable(cold('(a|)', { + a: true + })); + }); + + it('dispatchAction, in setAsNotDuplicate, should be called with \'reject\', and \'WORKSPACE\'', () => { + const decision = new DuplicateDecision( + 'reject', + DuplicateDecisionType.WORKSPACE); + + comp.setAsNotDuplicate(); + expect(compAsAny.dispatchAction).toHaveBeenCalledWith(decision); + }); + + it('dispatchAction, in clearDecision, should be called with blank, and \'WORKFLOW\'', () => { + const decision = new DuplicateDecision( + '', + DuplicateDecisionType.WORKSPACE); + + comp.clearDecision(); + expect(compAsAny.dispatchAction).toHaveBeenCalledWith(decision); + + }); + }); + + it('rejectForm.reset should be called by openModal', () => { + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkflowItem); + comp.match = matchWorkflowMock; + + fixture.detectChanges(); + spyOn(comp.rejectForm, 'reset').and.callThrough(); + + comp.openModal({}); + expect(comp.rejectForm.reset).toHaveBeenCalled(); + }); + + it('should not show any decision buttons when ready-only is true', () => { + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkflowItem); + comp.match = matchWorkflowMock; + comp.readOnly = true; + fixture.detectChanges(); + const button = de.query(By.css('.btn')); + expect(button).toBeNull(); + }); + }); + +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + match = matchWorkflowMock; +} diff --git a/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.ts b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.ts new file mode 100644 index 00000000000..eaa21688a1a --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/duplicate-match/duplicate-match.component.ts @@ -0,0 +1,306 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +import { Observable, of as observableOf } from 'rxjs'; +import { filter, take } from 'rxjs/operators'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; + +import { Item } from '../../../../core/shared/item.model'; +import { SubmissionService } from '../../../submission.service'; +import { DetectDuplicateService } from '../detect-duplicate.service'; +import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; +import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; +import { DuplicateDecisionValue } from '../models/duplicate-decision-value'; +import { DuplicateDecision } from '../models/duplicate-decision.model'; +import { DuplicateDecisionType } from '../models/duplicate-decision-type'; +import { isNotEmpty } from '../../../../shared/empty.util'; +import { SectionsService } from '../../sections.service'; +import { DuplicateMatchMetadataDetailConfig } from '../models/duplicate-detail-metadata.model'; +import { DetectDuplicateMatch } from '../../../../core/submission/models/workspaceitem-section-deduplication.model'; +import { environment } from '../../../../../environments/environment'; + +/** + * This component shows a single possible duplication within the duplications section. + */ +@Component({ + selector: 'ds-duplicate-match', + templateUrl: 'duplicate-match.component.html', +}) + +export class DuplicateMatchComponent implements OnInit { + /** + * The submission section ID. + * @type {string} + */ + @Input() sectionId: string; + + /** + * The item ID of a possible duplication for which you want to record a decision. + * @type {string} + */ + @Input() itemId: string; + + /** + * A possible duplication match object. + * @type {DetectDuplicateMatch} + */ + @Input() match: DetectDuplicateMatch; + + /** + * Representing the possibility to take decisions on the matches + * @type {boolean} + */ + @Input() readOnly = false; + + /** + * The submission ID. + * @type {string} + */ + @Input() submissionId: string; + + /** + * The index number to sort the possible duplication matches inside the pagination system. + * @type {string} + */ + @Input() index: string; + + /** + * The search result object. + * @type {object} + */ + object = { hitHighlights: [] }; + + /** + * A possible duplication match object item. + * @type {Item} + */ + item: Item; + + /** + * If TRUE the submission scope is the 'workflow'; 'workspace' otherwise. + * @type {boolean} + */ + isWorkFlow = false; + + /** + * If TRUE the submission decision will be rendered in the HTML output. + * @type {boolean} + */ + showSubmitterDecision = false; + + /** + * The list of decision types. + * @type {DuplicateDecisionType} + */ + decisionType: DuplicateDecisionType; + + /** + * The submitter decision translated text. + * @type {Observable} + */ + submitterDecision$: Observable; + + /** + * The submitter decision notes. + * @type {string} + */ + submitterNote: string; + + /** + * If TRUE, the possible duplication already has a saved decision. + * @type {boolean} + */ + hasDecision: boolean; + + /** + * The modal 'close' return value. + * @type {string} + */ + closeResult: string; + + /** + * The form where to write the reject decision notes. + * @type {FormGroup} + */ + rejectForm: FormGroup; + + /** + * The modal reference to the HTML. + * @type {NgbModalRef} + */ + modalRef: NgbModalRef; + + /** + * Combines a variable number of strings representing parts of a JSON-PATCH path. + * @type {JsonPatchOperationPathCombiner} + */ + pathCombiner: JsonPatchOperationPathCombiner; + + /** + * Use to change the Verify button label during the saving process. + * @type {Observable} + */ + public processingVerify: Observable = observableOf(false); + + /** + * Use to change the Reject button label during the saving process. + * @type {Observable} + */ + public processingReject: Observable = observableOf(false); + + /** + * Contains the CSS class for the submitter decision text. + * @type {string} + */ + decisionLabelClass: string; + + /** + * 'It is a duplication' button label. + * @type {Observable} + */ + duplicateBtnLabel$: Observable; + + /** + * 'Not a duplication' button label. + * @type {Observable} + */ + notDuplicateBtnLabel$: Observable; + + /** + * The list of the metadata, of the possible duplication, to show in HTML. + * @type {DuplicateMatchMetadataDetailConfig} + */ + metadataList: DuplicateMatchMetadataDetailConfig[]; + + /** + * Initialize instance variables. + * + * @param {DetectDuplicateService} detectDuplicateService + * @param {FormBuilder} formBuilder + * @param {NgbModal} modalService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {TranslateService} translate + */ + constructor(private detectDuplicateService: DetectDuplicateService, + private formBuilder: FormBuilder, + private modalService: NgbModal, + private operationsBuilder: JsonPatchOperationsBuilder, + private sectionService: SectionsService, + private submissionService: SubmissionService, + private translate: TranslateService) { + this.metadataList = environment.submission.detectDuplicate.metadataDetailsList || []; + } + + /** + * Initialize all instance variables and retrieve configuration. + */ + ngOnInit(): void { + this.isWorkFlow = this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkflowItem; + this.decisionType = this.isWorkFlow ? DuplicateDecisionType.WORKFLOW : DuplicateDecisionType.WORKSPACE; + this.item = Object.assign(new Item(), this.match.matchObject); + + this.rejectForm = this.formBuilder.group({ + reason: [''] + }); + + this.hasDecision = this.isWorkFlow ? + this.match.workflowDecision !== null + : this.match.submitterDecision !== null; + + if (this.match.submitterDecision) { + this.submitterDecision$ = (this.match.submitterDecision === DuplicateDecisionValue.Reject) ? + this.translate.get('submission.sections.detect-duplicate.not-duplicate') : + this.translate.get('submission.sections.detect-duplicate.duplicate'); + this.decisionLabelClass = (this.match.submitterDecision === DuplicateDecisionValue.Reject) ? 'badge-success' : 'badge-warning'; + this.submitterNote = this.match.submitterNote; + } else { + this.submitterDecision$ = this.translate.get('submission.sections.detect-duplicate.no-decision'); + this.decisionLabelClass = 'badge-light'; + } + + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId); + + this.duplicateBtnLabel$ = this.isWorkFlow ? + ((this.match.submitterDecision === DuplicateDecisionValue.Verify) ? + this.translate.get('submission.sections.detect-duplicate.confirm-duplicate') : + this.translate.get('submission.sections.detect-duplicate.duplicate-ctrl')) + : this.translate.get('submission.sections.detect-duplicate.duplicate'); + + this.notDuplicateBtnLabel$ = (this.isWorkFlow && this.match.submitterDecision === DuplicateDecisionValue.Reject) ? + this.translate.get('submission.sections.detect-duplicate.confirm-not-duplicate') : + this.translate.get('submission.sections.detect-duplicate.not-duplicate'); + } + + /** + * Save the 'It is a duplication' decision. + */ + setAsDuplicate() { + this.processingVerify = observableOf(true); + const decision = new DuplicateDecision( + DuplicateDecisionValue.Verify, + this.decisionType, + this.rejectForm.get('reason').value); + + this.dispatchAction(decision); + this.modalRef.dismiss(); + } + + /** + * Save the 'It is not a duplication' decision. + */ + setAsNotDuplicate() { + this.processingReject = observableOf(true); + const decision = new DuplicateDecision( + DuplicateDecisionValue.Reject, + this.decisionType); + + this.dispatchAction(decision); + } + + /** + * Removes the previously saved decision. + */ + clearDecision() { + const decision = new DuplicateDecision( + DuplicateDecisionValue.Undo, + this.decisionType); + + this.dispatchAction(decision); + } + + /** + * Save the decision on the backend. + * + * @param {DuplicateDecision} decision + * the object containing the decision + */ + private dispatchAction(decision: DuplicateDecision): void { + const pathDecision = Array.of('matches', this.itemId, this.isWorkFlow ? 'workflowDecision' : 'submitterDecision').join('/'); + const payload = { + value: isNotEmpty(decision.value) ? decision.value : null, + note: isNotEmpty(decision.note) ? decision.note : null + }; + + // dispatch patch operation only when section is active + this.sectionService.isSectionActive(this.submissionId, this.sectionId).pipe( + filter((isActive: boolean) => isActive), + take(1)) + .subscribe(() => { + this.operationsBuilder.add(this.pathCombiner.getPath(pathDecision), payload, false, true); + this.detectDuplicateService.saveDuplicateDecision(this.submissionId, this.sectionId); + }); + } + + /** + * Open the decision modal. + */ + openModal(modal) { + this.rejectForm.reset(); + this.modalRef = this.modalService.open(modal); + } + +} diff --git a/src/app/submission/sections/detect-duplicate/models/duplicate-decision-type.ts b/src/app/submission/sections/detect-duplicate/models/duplicate-decision-type.ts new file mode 100644 index 00000000000..57a30a13b3b --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/models/duplicate-decision-type.ts @@ -0,0 +1,5 @@ +export enum DuplicateDecisionType { + WORKSPACE = 'WORKSPACE', + WORKFLOW = 'WORKFLOW', + ADMIN = 'ADMIN' +} diff --git a/src/app/submission/sections/detect-duplicate/models/duplicate-decision-value.ts b/src/app/submission/sections/detect-duplicate/models/duplicate-decision-value.ts new file mode 100644 index 00000000000..298e33ca396 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/models/duplicate-decision-value.ts @@ -0,0 +1,5 @@ +export enum DuplicateDecisionValue { + Reject = 'reject', + Verify = 'verify', + Undo = '' +} diff --git a/src/app/submission/sections/detect-duplicate/models/duplicate-decision.model.ts b/src/app/submission/sections/detect-duplicate/models/duplicate-decision.model.ts new file mode 100644 index 00000000000..e18053e1342 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/models/duplicate-decision.model.ts @@ -0,0 +1,57 @@ +import { DuplicateDecisionType } from './duplicate-decision-type'; +import { DuplicateDecisionValue } from './duplicate-decision-value'; +import { isNotNull } from '../../../../shared/empty.util'; + +export class DuplicateDecision { + + private _value: DuplicateDecisionValue; + private _type: DuplicateDecisionType; + private _note: string; + private _date: any; + + public constructor(value = null, type = null, note = null) { + if (isNotNull(value)) { + this.value = value; + } + + if (isNotNull(type)) { + this.type = type; + } + + if (isNotNull(note)) { + this.note = note; + } + } + + get value(): DuplicateDecisionValue { + return this._value; + } + + set value(value: DuplicateDecisionValue) { + this._value = value; + } + + get type(): DuplicateDecisionType { + return this._type; + } + + set type(value: DuplicateDecisionType) { + this._type = value; + } + + get date(): any { + return this._date; + } + + set date(value: any) { + this._date = value; + } + + get note(): string { + return this._note; + } + + set note(value: string) { + this._note = value; + } +} diff --git a/src/app/submission/sections/detect-duplicate/models/duplicate-detail-metadata.model.ts b/src/app/submission/sections/detect-duplicate/models/duplicate-detail-metadata.model.ts new file mode 100644 index 00000000000..9e5b63f1580 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/models/duplicate-detail-metadata.model.ts @@ -0,0 +1,7 @@ +/** + * An Interface representing the duplicate match + */ +export interface DuplicateMatchMetadataDetailConfig { + label: string; + name: string; +} diff --git a/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.html b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.html new file mode 100644 index 00000000000..9485e8d7879 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.html @@ -0,0 +1,32 @@ + + + + + + + + + + + +
    +
  • + +
  • +
+
+
diff --git a/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.spec.ts b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.spec.ts new file mode 100644 index 00000000000..1a67592edd5 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.spec.ts @@ -0,0 +1,292 @@ +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 { 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 { SubmissionFormsConfigDataService } from '../../../core/config/submission-forms-config-data.service'; +import { SectionDataObject } from '../models/section-data.model'; +import { SectionsType } from '../sections-type'; +import { mockSubmissionCollectionId, mockSubmissionId } from '../../../shared/mocks/submission.mock'; +import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { SubmissionSectionDetectDuplicateComponent } from './section-detect-duplicate.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 { DetectDuplicateService } from './detect-duplicate.service'; +import { getMockDetectDuplicateService } from '../../../shared/mocks/mock-detect-duplicate-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 { + DetectDuplicateMatch, + WorkspaceitemSectionDetectDuplicateObject +} from '../../../core/submission/models/workspaceitem-section-deduplication.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(): SubmissionFormsConfigDataService { + 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' + } + ] + }, +}); + +const mockMatch: DetectDuplicateMatch = { + submitterDecision: null, + submitterNote: null, + submitterTime: null, + + workflowDecision: null, + workflowNote: null, + workflowTime: null, + + adminDecision: null, + + matchObject: mockItem +}; + +const sectionData: WorkspaceitemSectionDetectDuplicateObject = { + matches: { + 'fake-match-id': mockMatch + } +}; + +const sectionObject: SectionDataObject = { + config: 'https://dspace.org/api/config/submissionforms/detect-duplicate', + mandatory: true, + opened: true, + data: sectionData, + errorsToShow: [], + serverValidationErrors: [], + header: 'submit.progressbar.detect-duplicate', + id: 'detect-duplicate', + sectionType: SectionsType.DetectDuplicate, + sectionVisibility: null +}; + +describe('SubmissionSectionDetectDuplicateComponent test suite', () => { + let comp: SubmissionSectionDetectDuplicateComponent; + let compAsAny: any; + let fixture: ComponentFixture; + let submissionServiceStub: any; + let sectionsServiceStub: any; + let formService: any; + let formOperationsService: any; + let formBuilderService: any; + let collectionDataService: any; + + const mockDetectDuplicateService: any = getMockDetectDuplicateService(); + 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: [ + SubmissionSectionDetectDuplicateComponent, + TestComponent, + ObjNgFor, + VarDirective, + ], + providers: [ + { provide: CollectionDataService, useValue: getMockCollectionDataService() }, + { provide: SectionFormOperationsService, useValue: getMockFormOperationsService() }, + { provide: FormService, useValue: getMockFormService() }, + { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder }, + { provide: SubmissionFormsConfigDataService, 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: DetectDuplicateService, useValue: mockDetectDuplicateService }, + { provide: PaginationService, useValue: paginationService }, + ChangeDetectorRef, + FormBuilderService, + SubmissionSectionDetectDuplicateComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents().then(); + })); + + // First test to check the correct component creation + describe('', () => { + let testComp: TestComponent; + let testFixture: ComponentFixture; + + // synchronous beforeEach + beforeEach(() => { + mockDetectDuplicateService.getDuplicateMatchesByScope.and.returnValue(observableOf(sectionData)); + const html = ` + `; + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + testComp = testFixture.componentInstance; + }); + + afterEach(() => { + testFixture.destroy(); + }); + + it('should create SubmissionSectionDetectDuplicateComponent', inject([SubmissionSectionDetectDuplicateComponent], (app: SubmissionSectionDetectDuplicateComponent) => { + expect(app).toBeDefined(); + })); + }); + + describe('', () => { + beforeEach(() => { + fixture = TestBed.createComponent(SubmissionSectionDetectDuplicateComponent); + comp = fixture.componentInstance; + compAsAny = comp; + submissionServiceStub = TestBed.inject(SubmissionService); + sectionsServiceStub = TestBed.inject(SectionsService); + 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; + }); + + it('Should init section properly - with workflow', () => { + collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection)); + sectionsServiceStub.getSectionErrors.and.returnValue(observableOf([])); + sectionsServiceStub.isSectionReadOnly.and.returnValue(observableOf(false)); + mockDetectDuplicateService.getDuplicateMatchesByScope.and.returnValue(observableOf(sectionData)); + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkflowItem); + spyOn(compAsAny, 'getSectionStatus').and.returnValue(observableOf(true)); + + comp.onSectionInit(); + fixture.detectChanges(); + + expect(comp.isWorkFlow).toBeTruthy(); + expect(comp.sectionData$).toBeObservable(cold('(a|)', { + a: sectionData + })); + }); + + it('Should init section properly - with workspace', () => { + collectionDataService.findById.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection)); + sectionsServiceStub.getSectionErrors.and.returnValue(observableOf([])); + sectionsServiceStub.isSectionReadOnly.and.returnValue(observableOf(false)); + mockDetectDuplicateService.getDuplicateMatchesByScope.and.returnValue(observableOf(sectionData)); + compAsAny.submissionService.getSubmissionScope.and.returnValue(SubmissionScopeType.WorkspaceItem); + spyOn(compAsAny, 'getSectionStatus').and.returnValue(observableOf(true)); + + comp.onSectionInit(); + fixture.detectChanges(); + + expect(comp.isWorkFlow).toBeFalsy(); + expect(comp.sectionData$).toBeObservable(cold('(a|)', { + a: sectionData + })); + }); + + it('Should return TRUE if the sectionData is empty', () => { + compAsAny.sectionData$ = observableOf({ matches: { } }); + expect(compAsAny.getSectionStatus()).toBeObservable(cold('(a|)', { + a: true + })); + }); + + it('Should return FALSE if the sectionData is not empty', () => { + compAsAny.sectionData$ = observableOf(sectionData); + expect(compAsAny.getSectionStatus()).toBeObservable(cold('(a|)', { + a: false + })); + }); + + it('Should return the length of the sectionData$', () => { + compAsAny.sectionData$ = observableOf({ matches: [{ dummy: 1 }, { dummy: 2 }] }); + expect(compAsAny.getTotalMatches()).toBeObservable(cold('(a|)', { + a: 2 + })); + }); + }); + +}); + +// declare a test component +@Component({ + selector: 'ds-test-cmp', + template: `` +}) +class TestComponent { + +} diff --git a/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.ts b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.ts new file mode 100644 index 00000000000..24c969cb8d5 --- /dev/null +++ b/src/app/submission/sections/detect-duplicate/section-detect-duplicate.component.ts @@ -0,0 +1,163 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +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 { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SubmissionService } from '../../submission.service'; +import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; +import { AlertType } from '../../../shared/alert/aletr-type'; +import { DetectDuplicateService } from './detect-duplicate.service'; +import { SectionsService } from '../sections.service'; +import { WorkspaceitemSectionDetectDuplicateObject } from '../../../core/submission/models/workspaceitem-section-deduplication.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; + +/** + * This component represents a section that contains possible duplications. + */ +@Component({ + selector: 'ds-submission-section-detect-duplicate', + templateUrl: './section-detect-duplicate.component.html', + changeDetection: ChangeDetectionStrategy.Default +}) + +@renderSectionFor(SectionsType.DetectDuplicate) +export class SubmissionSectionDetectDuplicateComponent extends SectionModelComponent { + /** + * The Alert categories. + * @type {AlertType} + */ + public AlertTypeEnum = AlertType; + + /** + * Variable to track if the section is loading. + * @type {boolean} + */ + public isLoading = true; + + /** + * The object containing the list of the possible duplications. + * @type {Observable} + */ + public sectionData$: Observable; + + /** + * The list of the possible duplications. + * @type {Object} + */ + public matches = {}; + + /** + * The pagination system configuration for HTML listing. + * @type {PaginationComponentOptions} + */ + config$: Observable; + + /** + * The duplications list sort options. + * @type {SortOptions} + */ + sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC); + + /** + * If TRUE the submission scope is the 'workflow'; 'workspace' otherwise. + * @type {boolean} + */ + isWorkFlow = false; + + /** + * The list of the possible duplications. + * @type {PaginationComponentOptions} + */ + disclaimer: Observable; + + /** + * Initialize instance variables. + * + * @param {DetectDuplicateService} detectDuplicateService + * @param {PaginationService} paginationService + * @param {TranslateService} translate + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ + constructor(protected detectDuplicateService: DetectDuplicateService, + protected paginationService: PaginationService, + 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); + } + + /** + * Initialize all instance variables and retrieve configuration. + */ + onSectionInit() { + const config = new PaginationComponentOptions(); + config.id = 'dup'; + config.pageSize = 2; + config.pageSizeOptions = [1, 2, 5]; + this.config$ = this.paginationService.getCurrentPagination(config.id, config); + + if (this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkflowItem) { + this.isWorkFlow = true; + this.disclaimer = this.translate.get('submission.sections.detect-duplicate.disclaimer-ctrl'); + } else { + this.isWorkFlow = false; + this.disclaimer = this.translate.get('submission.sections.detect-duplicate.disclaimer'); + } + + this.sectionData$ = this.detectDuplicateService.getDuplicateMatchesByScope(this.submissionId, this.sectionData.id, this.isWorkFlow); + + this.isLoading = false; + } + + /** + * Get section status. + * + * @return Observable + * the section status + */ + public getSectionStatus(): Observable { + return this.sectionData$.pipe( + map((totalMatches: any) => { + let output = false; + if (Object.keys(totalMatches.matches).length === 0) { + output = true; + } + return output; + }) + ); + } + + /** + * Get the count of the possible duplications. + * + * @return Observable + * the number of possible duplications + */ + getTotalMatches(): Observable { + return this.sectionData$.pipe( + map((totalMatches: any) => Object.keys(totalMatches.matches).length) + ); + } + + /** + * Unsubscribe from all subscriptions, if needed. + */ + onSectionDestroy(): void { + return; + } + +} diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts index 6b6f839b7ca..c9f4debaf9d 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', + DetectDuplicate = 'detect-duplicate', } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index cab4f19c335..c5ffc6db91d 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -46,6 +46,9 @@ import { import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; +import { SubmissionSectionDetectDuplicateComponent } from './sections/detect-duplicate/section-detect-duplicate.component'; +import { DuplicateMatchComponent } from './sections/detect-duplicate/duplicate-match/duplicate-match.component'; +import { DetectDuplicateService } from './sections/detect-duplicate/detect-duplicate.service'; import { ThemedSubmissionEditComponent } from './edit/themed-submission-edit.component'; import { ThemedSubmissionSubmitComponent } from './submit/themed-submission-submit.component'; import { ThemedSubmissionImportExternalComponent } from './import-external/themed-submission-import-external.component'; @@ -65,7 +68,12 @@ import { MetadataInformationComponent } from './sections/sherpa-policies/metadata-information/metadata-information.component'; import { SectionFormOperationsService } from './sections/form/section-form-operations.service'; - +import { MyDspaceSearchModule } from '../my-dspace-page/my-dspace-search.module'; +/* +import { + ThemedItemListPreviewComponent +} from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +*/ const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator SubmissionSectionUploadComponent, @@ -74,6 +82,7 @@ const ENTRY_COMPONENTS = [ SubmissionSectionCcLicensesComponent, SubmissionSectionAccessesComponent, SubmissionSectionSherpaPoliciesComponent, + SubmissionSectionDetectDuplicateComponent, ]; const DECLARATIONS = [ @@ -102,6 +111,7 @@ const DECLARATIONS = [ PublisherPolicyComponent, PublicationInformationComponent, MetadataInformationComponent, + DuplicateMatchComponent ]; @NgModule({ @@ -109,6 +119,7 @@ const DECLARATIONS = [ CommonModule, CoreModule.forRoot(), SharedModule, + MyDspaceSearchModule, StoreModule.forFeature('submission', submissionReducers, storeModuleConfig as StoreConfig), EffectsModule.forFeature(submissionEffects), JournalEntitiesModule.withEntryComponents(), @@ -130,6 +141,7 @@ const DECLARATIONS = [ SubmissionUploadsConfigDataService, SubmissionAccessesConfigDataService, SectionAccessesService, + DetectDuplicateService, SectionFormOperationsService, ] }) diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index 1e2be5b6121..4a4ab095a1f 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -42,6 +42,9 @@ import { storeModuleConfig } from '../app.reducer'; import { environment } from '../../environments/environment'; import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service'; import { SubmissionJsonPatchOperationsServiceStub } from '../shared/testing/submission-json-patch-operations-service.stub'; +import {NotificationOptions} from '../shared/notifications/models/notification-options.model'; +import {getMockScrollToService} from '../shared/mocks/scroll-to-service.mock'; +import {ScrollToService} from '@nicky-lenaers/ngx-scroll-to'; describe('SubmissionService test suite', () => { const collectionId = '43fe1f8c-09a6-4fcf-9c78-5d4fed8f2c8f'; @@ -198,6 +201,7 @@ describe('SubmissionService test suite', () => { }, isLoading: false, savePending: false, + saveDecisionPending: false, depositPending: false } } @@ -386,6 +390,7 @@ describe('SubmissionService test suite', () => { { provide: Router, useValue: router }, { provide: SubmissionRestService, useValue: restService }, { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, + { provide: ScrollToService, useValue: getMockScrollToService() }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestServce }, { provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsService }, @@ -779,6 +784,21 @@ describe('SubmissionService test suite', () => { }); }); + describe('getSubmissionDuplicateDecisionProcessingStatus', () => { + it('should return submission save-decision status', () => { + spyOn((service as any).store, 'select').and.returnValue(hot('-a', { + a: subState.objects[826] + })); + + const result = service.getSubmissionDuplicateDecisionProcessingStatus('826'); + const expected = cold('aa', { + a: false + }); + + expect(result).toBeObservable(expected); + }); + }); + describe('hasUnsavedModification', () => { it('should call jsonPatchOperationService hasPendingOperation observable', () => { (service as any).jsonPatchOperationService.hasPendingOperations = jasmine.createSpy('hasPendingOperations') @@ -862,6 +882,16 @@ describe('SubmissionService test suite', () => { expect((service as any).notificationsService.info).toHaveBeenCalledWith(null, 'submission.sections.general.metadata-extracted-new-section', null, true); })); + it('should use the correct message when the sectionId is equal to \'detect-duplicate\'', fakeAsync(() => { + const dtSetctionId = 'detect-duplicate'; + spyOn((service as any).translate, 'get').and.returnValue(observableOf(dtSetctionId)); + spyOn((service as any).notificationsService, 'warning'); + + service.notifyNewSection(submissionId, dtSetctionId); + flush(); + + expect((service as any).notificationsService.warning).toHaveBeenCalledWith(null, 'submission.sections.detect-duplicate.duplicate-detected', new NotificationOptions(10000)); + })); }); describe('redirectToMyDSpace', () => { diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 9eb8cf110a5..c1b916a5e87 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -46,6 +46,8 @@ import { environment } from '../../environments/environment'; import { SubmissionJsonPatchOperationsService } from '../core/submission/submission-json-patch-operations.service'; import { SubmissionSectionObject } from './objects/submission-section-object.model'; import { SubmissionError } from './objects/submission-error.model'; +import {ScrollToConfigOptions, ScrollToService} from '@nicky-lenaers/ngx-scroll-to'; +import {NotificationOptions} from '../shared/notifications/models/notification-options.model'; /** * A service that provides methods used in submission process. @@ -65,6 +67,7 @@ export class SubmissionService { private workspaceLinkPath = 'workspaceitems'; private workflowLinkPath = 'workflowitems'; + /** * Initialize service variables * @param {NotificationsService} notificationsService @@ -72,6 +75,7 @@ export class SubmissionService { * @param {Router} router * @param {RouteService} routeService * @param {Store} store + * @param {ScrollToService} scrollToService * @param {TranslateService} translate * @param {SearchService} searchService * @param {RequestService} requestService @@ -82,6 +86,7 @@ export class SubmissionService { protected router: Router, protected routeService: RouteService, protected store: Store, + protected scrollToService: ScrollToService, protected translate: TranslateService, protected searchService: SearchService, protected requestService: RequestService, @@ -322,6 +327,7 @@ export class SubmissionService { Object.keys(sections) .filter((sectionId) => !this.isSectionHidden(sections[sectionId] as SubmissionSectionObject)) .filter((sectionId) => !sections[sectionId].enabled) + .filter((sectionId) => sections[sectionId].sectionType !== SectionsType.DetectDuplicate) .forEach((sectionId) => { const sectionObject: SectionDataObject = Object.create({}); sectionObject.header = sections[sectionId].header; @@ -433,6 +439,21 @@ export class SubmissionService { startWith(false)); } + /** + * Return the save-decision status of the submission + * + * @param submissionId + * The submission id + * @return Observable + * observable with submission save-decision status + */ + getSubmissionDuplicateDecisionProcessingStatus(submissionId: string): Observable { + return this.getSubmissionObject(submissionId).pipe( + map((state: SubmissionObjectEntry) => state.saveDecisionPending), + distinctUntilChanged(), + startWith(false)); + } + /** * Return whether submission unsaved modification are present * @@ -482,8 +503,20 @@ export class SubmissionService { * The section type */ notifyNewSection(submissionId: string, sectionId: string, sectionType?: SectionsType) { - const m = this.translate.instant('submission.sections.general.metadata-extracted-new-section', { sectionId }); - this.notificationsService.info(null, m, null, true); + if (sectionType === SectionsType.DetectDuplicate || sectionId === 'detect-duplicate') { + this.setActiveSection(submissionId, sectionId); + const msg = this.translate.instant('submission.sections.detect-duplicate.duplicate-detected', {sectionId}); + this.notificationsService.warning(null, msg, new NotificationOptions(10000)); + const config: ScrollToConfigOptions = { + target: sectionId, + offset: -70 + }; + + this.scrollToService.scrollTo(config); + } else { + const m = this.translate.instant('submission.sections.general.metadata-extracted-new-section', {sectionId}); + this.notificationsService.info(null, m, null, true); + } } /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d2bd425df04..95477aff4bd 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3890,6 +3890,44 @@ "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", + "submission.sections.detect-duplicate.clear-decision": "Undo", + + "submission.sections.detect-duplicate.clear-decision-help": "Click for clear the decision about this pontential duplicate", + + "submission.sections.detect-duplicate.confirm-duplicate": "Confirm (It's a duplicate)", + + "submission.sections.detect-duplicate.confirm-not-duplicate": "Confirm (It's not a duplicate)", + + "submission.sections.detect-duplicate.decision-success-notice": "Choice registered successfully", + + "submission.sections.detect-duplicate.disclaimer": "The system has identified some potential duplicates. Please carefully review the list and flag each occurency with the appropriate choice or discard this submission.", + + "submission.sections.detect-duplicate.disclaimer-ctrl": "The system has identified some potential duplicates. Please carefully review the list and the submitter comments and perform the appropriate action.", + + "submission.sections.detect-duplicate.disclaimer-no-match": "All your feedback on potential duplicates have been registered correctly.", + + "submission.sections.detect-duplicate.duplicate": "It's a duplicate", + + "submission.sections.detect-duplicate.duplicate-ctrl": "Mark the record to merge", + + "submission.sections.detect-duplicate.duplicate-detected": "Potential duplicate detected", + + "submission.sections.detect-duplicate.duplicate-help": "Click here if this is a duplicate of your item", + + "submission.sections.detect-duplicate.note-help": "Please enter your reason for the duplication into the box below.", + + "submission.sections.detect-duplicate.note-placeholder": "Describe the reason of duplication", + + "submission.sections.detect-duplicate.no-decision": "No decision yet", + + "submission.sections.detect-duplicate.not-duplicate": "It's not a duplicate", + + "submission.sections.detect-duplicate.not-duplicate-help": "Click here if this is not a duplicate of your item", + + "submission.sections.detect-duplicate.submitter-decision": "Submitter decision:", + + "submission.sections.detect-duplicate.submitter-note": "Submitter note:", + "submission.sections.describe.relationship-lookup.close": "Close", "submission.sections.describe.relationship-lookup.external-source.added": "Successfully added local entry to the selection", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 276d2d71502..2aa168fea32 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -179,6 +179,12 @@ export class DefaultAppConfig implements AppConfig { ] } + }, + detectDuplicate: { + // NOTE: list of additional item metadata to show for duplicate match presentation list + metadataDetailsList: [ + { label: 'Document type', name: 'dc.type' } + ] } }; diff --git a/src/config/submission-config.interface.ts b/src/config/submission-config.interface.ts index a63af45e38a..0a064912de6 100644 --- a/src/config/submission-config.interface.ts +++ b/src/config/submission-config.interface.ts @@ -1,4 +1,5 @@ import { Config } from './config.interface'; +import { DuplicateMatchMetadataDetailConfig } from '../app/submission/sections/detect-duplicate/models/duplicate-detail-metadata.model'; interface AutosaveConfig extends Config { metadata: string[]; @@ -26,8 +27,13 @@ export interface ConfidenceIconConfig extends Config { style: string; } +interface DetectDuplicateConfig extends Config { + metadataDetailsList: DuplicateMatchMetadataDetailConfig[]; +} + export interface SubmissionConfig extends Config { autosave: AutosaveConfig; typeBind: TypeBindConfig; icons: IconsConfig; + detectDuplicate: DetectDuplicateConfig; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 19eec26a140..e7a77decc0c 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -148,6 +148,12 @@ export const environment: BuildConfig = { }, ] } + }, + detectDuplicate: { + // NOTE: list of additional item metadata to show for duplicate match presentation list + metadataDetailsList: [ + { label: 'Document type', name: 'dc.type' } + ] } },