this.statusComponent.errorLoadingFile(e.message))
}
+ onFormatChange(format: string) {
+ this.statusComponent.changeFormat(format)
+ }
}
diff --git a/apps/metadata-converter/src/app/components/record-form/record-form.component.html b/apps/metadata-converter/src/app/components/record-form/record-form.component.html
index e3447599a4..63956f44eb 100644
--- a/apps/metadata-converter/src/app/components/record-form/record-form.component.html
+++ b/apps/metadata-converter/src/app/components/record-form/record-form.component.html
@@ -29,29 +29,39 @@
>
-
-
-
-
-
+
+
+ Add a owner organization to this record
+
+
+
+
+
+
+
@@ -119,7 +129,7 @@
(fieldValueChange)="set($event, 'position')"
(confirm)="emitChangedRecord()"
>
-
+
@@ -370,7 +380,7 @@
label="Type"
[options]="['service', 'download', 'link']"
[fieldValue]="get().type"
- (fieldValueChange)="set(getDistributionForType($event))"
+ (fieldValueChange)="set(getOnlineResourceForType($event))"
(confirm)="emitChangedRecord()"
>
{
- this.newMetadata.emit(output)
- const time = Math.round(performance.now() - start)
- this.status = `Converting to ISO9139... Done (${time} ms).`
- })
- .catch((e) => {
- this.status = `Converting to ISO9139... Failed: ${
- e instanceof Error ? e.message : e
- }`
- console.error(e)
- })
+ this._currentRecord = value
+ this.convertRecordToXml(value)
}
@Input() set currentMetadata(value: string) {
const start = performance.now()
- this.status = 'Converting to native format...'
+ this.status = 'Converting to CatalogRecord...'
this.xmlToRecord(value)
.then((output) => {
+ this._currentRecord = output
this.newRecordNative.emit(output)
this.newMetadata.emit(value)
const time = Math.round(performance.now() - start)
- this.status = `Converting to native format... Done (${time} ms).`
+ this.status = `Converting to CatalogRecord... Done (${time} ms).`
})
.catch((e) => {
- this.status = `Converting to native format... Failed: ${
+ this.status = `Converting to CatalogRecord... Failed: ${
e instanceof Error ? e.message : e
}`
console.error(e)
@@ -48,6 +41,8 @@ export class StatusComponent {
@Output() newRecordNative = new EventEmitter()
@Output() newMetadata = new EventEmitter()
+ currentConverter: BaseConverter = null
+
status = 'Standing by.'
startLoadingFile() {
@@ -64,15 +59,60 @@ export class StatusComponent {
this.status = `Reading file... Failed`
}
- private recordToXml(record: CatalogRecord) {
- const converter = this.referenceMetadata
- ? findConverterForDocument(this.referenceMetadata)
- : new Iso191153Converter()
- return converter.writeRecord(record, this.referenceMetadata)
+ public changeFormat(format: string) {
+ const converterClass = FORMATS[format]
+ if (!converterClass) {
+ throw new Error(`Metadata format ${format} not supported`)
+ }
+ this.referenceMetadata = ''
+ this.convertRecordToXml(this._currentRecord, new converterClass())
+ }
+
+ private convertRecordToXml(
+ record: CatalogRecord,
+ currentConverter?: BaseConverter
+ ) {
+ const start = performance.now()
+ const converter = currentConverter ?? this.currentConverter
+ const converterName = getFormatName(converter)
+ this.status = `Converting to ${converterName}...`
+ this.newMetadata.emit('')
+ this.recordToXml(record, currentConverter)
+ .then((output) => {
+ this.newMetadata.emit(output)
+ const time = Math.round(performance.now() - start)
+ this.status = `Converting to ${converterName}... Done (${time} ms).`
+ })
+ .catch((e) => {
+ this.status = `Converting to ${converterName}... Failed: ${
+ e instanceof Error ? e.message : e
+ }`
+ console.error(e)
+ })
+ }
+
+ private recordToXml(
+ record: CatalogRecord,
+ currentConverter?: BaseConverter
+ ) {
+ try {
+ this.currentConverter = currentConverter
+ ? currentConverter
+ : this.referenceMetadata
+ ? findConverterForDocument(this.referenceMetadata)
+ : new Iso191153Converter()
+ return this.currentConverter.writeRecord(record, this.referenceMetadata)
+ } catch (e) {
+ return Promise.reject(e)
+ }
}
private xmlToRecord(metadata: string) {
- const converter = findConverterForDocument(metadata)
- return converter.readRecord(metadata)
+ try {
+ this.currentConverter = findConverterForDocument(metadata)
+ return this.currentConverter.readRecord(metadata)
+ } catch (e) {
+ return Promise.reject(e)
+ }
}
}
diff --git a/apps/metadata-converter/src/app/md-formats.ts b/apps/metadata-converter/src/app/md-formats.ts
new file mode 100644
index 0000000000..39a6cb8052
--- /dev/null
+++ b/apps/metadata-converter/src/app/md-formats.ts
@@ -0,0 +1,18 @@
+import {
+ BaseConverter,
+ DcatApConverter,
+ Iso191153Converter,
+ Iso19139Converter,
+} from '@geonetwork-ui/api/metadata-converter'
+
+export const FORMATS = {
+ 'ISO 19139': Iso19139Converter,
+ 'ISO 19115-3': Iso191153Converter,
+ 'DCAT-AP': DcatApConverter,
+}
+
+export function getFormatName(converter: BaseConverter): string {
+ return Object.keys(FORMATS).reduce((prev, key) =>
+ converter instanceof FORMATS[key] ? key : prev
+ )
+}
diff --git a/apps/metadata-editor-e2e/project.json b/apps/metadata-editor-e2e/project.json
index fe64bd1e65..fed7995813 100644
--- a/apps/metadata-editor-e2e/project.json
+++ b/apps/metadata-editor-e2e/project.json
@@ -10,7 +10,8 @@
"cypressConfig": "apps/metadata-editor-e2e/cypress.config.js",
"devServerTarget": "metadata-editor:serve:development",
"testingType": "e2e",
- "browser": "chrome"
+ "browser": "chrome",
+ "port": "cypress-auto"
},
"configurations": {
"production": {
@@ -32,5 +33,5 @@
}
},
"tags": [],
- "implicitDependencies": ["metadata-editor"]
+ "implicitDependencies": ["metadata-editor", "database-dump"]
}
diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
index 89322e4ee5..053bf0a7f6 100644
--- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
+++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts
@@ -1,5 +1,69 @@
-describe('dashboard', () => {
+const fakeUser = {
+ username: 'user',
+ password: 'Abcde12!',
+ email: 'jitewaboh@lagify.com',
+ name: 'Homer',
+ surname: 'Simpson',
+}
+
+const gnBaseUrl = 'http://localhost:8080/geonetwork/srv/eng/'
+
+describe('dashboard (authenticated)', () => {
+ beforeEach(() => {
+ cy.login('admin', 'admin', false)
+ })
+
let pageOne
+ describe('avatar', () => {
+ describe('display avatar for user without gravatar hash', () => {
+ it('should display placeholder url', () => {
+ cy.visit(`${gnBaseUrl}admin.console#/organization`)
+ cy.get('#gn-btn-user-add').click()
+ cy.get('#username').type(fakeUser.username)
+ cy.get('#gn-user-password').type(fakeUser.password)
+ cy.get('#gn-user-password2').type(fakeUser.password)
+ cy.get('[name="name"]').type(fakeUser.name)
+ cy.get('[name="surname"]').type(fakeUser.surname)
+ cy.get('[name="email"]').type(fakeUser.email)
+ cy.get('.fa-save').click()
+
+ cy.visit(`${gnBaseUrl}admin.console#/settings`)
+ cy.get('[id="system/users/identicon"]').type('{selectAll}{del}')
+ cy.get('#gn-btn-settings-save').click()
+
+ cy.visit('/')
+ cy.get('gn-ui-avatar')
+ .children('img')
+ .should('have.attr', 'src')
+ .should('eq', 'https://www.gravatar.com/avatar/?d=mp')
+ })
+ it('should display monsterid', () => {
+ cy.visit(`${gnBaseUrl}admin.console#/settings`)
+ cy.get('[id="system/users/identicon"]').type(
+ '{selectAll}gravatar:monsterid'
+ )
+ cy.get('#gn-btn-settings-save').click()
+
+ cy.visit('/')
+ cy.get('gn-ui-avatar')
+ .children('img')
+ .should('have.attr', 'src')
+ .should('eq', 'https://www.gravatar.com/avatar/?d=monsterid')
+ })
+ })
+ describe('display avatar for user with hash', () => {
+ it('should display the correct profile picture', () => {
+ cy.login(fakeUser.username, fakeUser.password, true)
+ cy.get('gn-ui-avatar')
+ .children('img')
+ .should('have.attr', 'src')
+ .should(
+ 'eq',
+ 'https://www.gravatar.com/avatar/09abd59eb5653a7183ba812b8261f48b?d=monsterid'
+ )
+ })
+ })
+ })
describe('pagination', () => {
it('should display different results on click on arrow', () => {
cy.visit('/catalog/search')
@@ -36,7 +100,7 @@ describe('dashboard', () => {
})
describe('sorting', () => {
- it.only('should order the result list on click', () => {
+ it('should order the result list on click', () => {
cy.visit('/catalog/search')
cy.get('gn-ui-results-table')
.find('.table-row-cell')
@@ -93,7 +157,169 @@ describe('dashboard', () => {
.get('gn-ui-checkbox')
.first()
.click()
- cy.get('[data-test=selected-count]').contains('14 selected')
+ cy.get('[data-test=selected-count]').contains('15 selected')
+ })
+ })
+ describe('columns', () => {
+ beforeEach(() => {
+ cy.visit('/catalog/search')
+ })
+ it('should display the right info for unpublished records', () => {
+ cy.get('[data-cy="create-record"]').click()
+ cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea')
+ .as('abstractField')
+ .focus()
+ cy.get('@abstractField').type('draft abstract')
+ cy.editor_readFormUniqueIdentifier().then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('contain', 'draft abstract')
+ })
+ cy.visit('/my-space/my-draft')
+ cy.get('gn-ui-results-table').find('[data-cy="table-row"]').as('draft')
+ cy.get('@draft').should('have.length', 1)
+ cy.get('@draft')
+ .children('div')
+ .eq(4)
+ .find('span')
+ .invoke('text')
+ .should('eq', '')
+ cy.get('@draft')
+ .children('div')
+ .eq(5)
+ .should('include.text', 'Not published')
+ cy.get('@draft').children('div').eq(6).should('contain', ' - ')
+ cy.clearRecordDrafts()
+ })
+ it('should display the right info for published records', () => {
+ cy.visit('/catalog/search')
+ cy.get('md-editor-dashboard-menu').find('a').eq(5).click()
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .first()
+ .as('record')
+ cy.get('@record')
+ .children('div')
+ .eq(4)
+ .find('span')
+ .invoke('text')
+ .should('eq', 'admin admin')
+ cy.get('@record')
+ .children('div')
+ .eq(5)
+ .find('span')
+ .should('include.text', 'Published')
+ cy.get('@record').children('div').eq(6).should('not.contain', ' - ')
+ cy.clearRecordDrafts()
+ })
+ })
+
+ describe('navigation', () => {
+ beforeEach(() => {
+ cy.visit('/catalog/search')
+ })
+ describe('all records', () => {
+ it('should display the correct amount of records', () => {
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .should('have.length', '15')
+ })
+ })
+ describe('my records', () => {
+ it('should only display records I own', () => {
+ cy.get('md-editor-dashboard-menu').find('a').eq(5).click()
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .find('mat-icon')
+ .next()
+ .should('contain', 'admin admin')
+ })
+ it('should display the correct amount of records', () => {
+ cy.get('md-editor-dashboard-menu').find('a').eq(5).click()
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .should('have.length', '10')
+ })
+ it('should sort the records by title', () => {
+ cy.get('md-editor-dashboard-menu').find('a').eq(5).click()
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .first()
+ .invoke('text')
+ .then((firstRecord) => {
+ console.log(firstRecord)
+ cy.get('gn-ui-results-table')
+ .find('.table-header-cell')
+ .eq(1)
+ .click()
+ cy.get('gn-ui-results-table')
+ .find('[data-cy="table-row"]')
+ .first()
+ .invoke('text')
+ .should('not.eq', firstRecord)
+ })
+ })
+ })
+ })
+
+ describe('search', () => {
+ function checkDashboardFiltered() {
+ cy.get('gn-ui-autocomplete').type('velo{enter}')
+ cy.get('gn-ui-interactive-table')
+ .find('[data-cy="table-row"]')
+ .should('have.length', '1')
+ }
+ function checkAutocompleteSelected() {
+ cy.get('gn-ui-autocomplete').type('velo')
+ cy.get('mat-option').first().click()
+ cy.url().should('include', '/edit/accroche_velos')
+ }
+ describe('allRecords search input', () => {
+ beforeEach(() => {
+ cy.visit('/catalog/search')
+ })
+ it('should filter the dashboard based on the search input', () => {
+ checkDashboardFiltered()
+ })
+ it('should navigate to the record selected in the autocomplete', () => {
+ checkAutocompleteSelected()
+ })
+ it('should clear the search input when navigating to my records', () => {
+ cy.get('gn-ui-autocomplete').type('velo')
+ cy.get('md-editor-dashboard-menu').find('a').eq(5).click()
+ cy.get('gn-ui-autocomplete').should('have.value', '')
+ })
+ it('should hide the search input when navigating to my drafts', () => {
+ cy.get('md-editor-dashboard-menu').find('a').eq(6).click()
+ cy.get('gn-ui-autocomplete').should('not.exist')
+ })
})
+ describe('myRecords search input', () => {
+ beforeEach(() => {
+ cy.login('admin', 'admin', false)
+ cy.visit('/my-space/my-records')
+ })
+ it('should filter the dashboard based on the search input', () => {
+ checkDashboardFiltered()
+ })
+ it('should navigate to the record selected in the autocomplete', () => {
+ checkAutocompleteSelected()
+ })
+ it('should clear the search input when navigating to all records', () => {
+ cy.get('gn-ui-autocomplete').type('velo')
+ cy.get('md-editor-dashboard-menu').find('a').first().click()
+ cy.get('gn-ui-autocomplete').should('have.value', '')
+ })
+ })
+ })
+})
+
+describe('when the user is not logged in', () => {
+ beforeEach(() => {
+ cy.visit('/catalog/search')
+ })
+ it('redirects to the login page', () => {
+ cy.url().should('include', '/catalog.signin?redirect=')
})
})
diff --git a/apps/metadata-editor-e2e/src/e2e/edit.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts
new file mode 100644
index 0000000000..abed248ec3
--- /dev/null
+++ b/apps/metadata-editor-e2e/src/e2e/edit.cy.ts
@@ -0,0 +1,732 @@
+/* eslint-disable cypress/no-unnecessary-waiting */
+
+describe('editor form', () => {
+ let recordUuid: any
+ before(() => {
+ cy.login('admin', 'admin', false)
+ cy.viewport(1920, 2400)
+
+ cy.clearRecordDrafts()
+
+ // Clear any existing copy of the test record
+ cy.visit('/catalog/search')
+ cy.get('gn-ui-fuzzy-search input').type('station épuration{enter}')
+ cy.get('[data-cy="table-row"]')
+ .should('have.length.lt', 10) // making sure the records were updated
+ .then((rows$) => {
+ if (rows$.length === 1) {
+ return
+ }
+ // there is a copy: delete it
+ cy.get('[data-test="record-menu-button"]').eq(1).click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ cy.log('An existing copy of the test record was found and deleted.')
+ })
+
+ // Duplicate & publish the Stations d'épuration record
+ cy.get('gn-ui-fuzzy-search input').type(
+ '{selectAll}{del}station épuration{enter}'
+ )
+ cy.get('[data-cy="table-row"]')
+ .first()
+ .should('contain.text', "Stations d'épuration")
+ .find('[data-test="record-menu-button"]')
+ .click()
+ cy.get('[data-test="record-menu-duplicate-button"]').click()
+ cy.url().should('include', '/duplicate/')
+ cy.editor_readFormUniqueIdentifier().then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('exist')
+ })
+ cy.get('md-editor-publish-button').click()
+
+ // Open the copy
+ cy.visit('/catalog/search')
+ cy.get('gn-ui-fuzzy-search input').type('station épuration copy{enter}')
+ cy.get('[data-cy="table-row"]').first().children('div').eq(2).click()
+
+ cy.editor_readFormUniqueIdentifier().then((uuid) => {
+ recordUuid = uuid
+ })
+ })
+ beforeEach(() => {
+ cy.login('admin', 'admin', false)
+ cy.visit('/catalog/search')
+ cy.wrap(recordUuid).as('recordUuid')
+
+ cy.get('@recordUuid').then((recordUuid) => {
+ cy.visit(`/edit/${recordUuid}`)
+ })
+ // aliases
+ cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea').as(
+ 'abstractField'
+ )
+ cy.get('@abstractField').invoke('val').as('abstractFieldInitialValue')
+ cy.get('[data-cy=save-status]')
+ .invoke('attr', 'data-cy-value')
+ .as('saveStatus')
+ cy.get('[data-test=pageSelectorButtons]')
+ .find('gn-ui-button')
+ .eq(0)
+ .as('descriptionPageBtn')
+ cy.get('[data-test=pageSelectorButtons]')
+ .find('gn-ui-button')
+ .eq(1)
+ .as('resourcePageBtn')
+ cy.get('[data-test=pageSelectorButtons]')
+ .find('gn-ui-button')
+ .eq(2)
+ .as('accessContactPageBtn')
+ })
+
+ describe('form display', () => {
+ it('form shows correctly', () => {
+ cy.get('gn-ui-record-form').should('be.visible')
+ cy.get('gn-ui-record-form gn-ui-form-field').should('have.length.gt', 0)
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.screenshot({ capture: 'fullPage' })
+ })
+
+ it('keeps the draft record', () => {
+ cy.get('@abstractField').clear()
+ cy.get('@abstractField').type('modified abstract')
+ cy.get('@recordUuid').then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('contain', 'modified abstract')
+ })
+
+ cy.reload()
+ cy.get('@abstractField').invoke('val').should('eq', 'modified abstract')
+ cy.get('@saveStatus').should('eq', 'draft_changes_pending')
+
+ cy.clearRecordDrafts()
+
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@abstractField')
+ .invoke('val')
+ .should('contain', 'Cette couche de points reprend les informations')
+ })
+ })
+ describe('record fields', () => {
+ describe('header', () => {
+ describe('title', () => {
+ it('shows the title', () => {
+ cy.get('gn-ui-form-field')
+ .first()
+ .find('input')
+ .invoke('val')
+ .should(
+ 'eq',
+ "Stations d'épuration selon la directive Eaux Résiduelles Urbaines (91/271/CEE) en Wallonie (Copy)"
+ )
+ })
+ it('edits and saves the title', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field').first().find('input').clear()
+ cy.get('gn-ui-form-field')
+ .first()
+ .find('input')
+ .type('Test record modified')
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field')
+ .first()
+ .find('input')
+ .invoke('val')
+ .should('eq', 'Test record modified')
+ })
+ })
+ describe('abstract', () => {
+ it('shows the abstract', () => {
+ cy.get('@abstractField')
+ .invoke('val')
+ .should(
+ 'contain',
+ 'Cette couche de points reprend les informations'
+ )
+ })
+ it('edits and saves the abstract', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('@abstractField').clear()
+ cy.get('@abstractField').type('modified abstract')
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@abstractField')
+ .invoke('val')
+ .should('eq', 'modified abstract')
+ })
+ })
+ describe('graphic overview', () => {
+ it('shows the graphic overview', () => {
+ cy.get('gn-ui-image-input').should('be.visible')
+ })
+ it('allows to delete images from the graphic overview', () => {
+ cy.get('gn-ui-image-input').find('img').should('have.length', 1)
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-image-input').find('gn-ui-button').eq(1).click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-image-input').find('img').should('have.length', 0)
+ })
+ it('allows to add overviews by URL', () => {
+ cy.get('gn-ui-image-input')
+ .find('gn-ui-button')
+ .find('button')
+ .click()
+ cy.get('gn-ui-url-input').should('be.visible')
+ })
+ it('adds overviews', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-overviews label').selectFile(
+ 'src/fixtures/sample.png'
+ )
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-image-input').find('img').should('have.length', 1)
+ })
+ it('allows to add an alternate text', () => {
+ cy.get('gn-ui-image-input').find('gn-ui-button').eq(2).click()
+ cy.get('gn-ui-image-input')
+ .find('gn-ui-text-input')
+ .should('be.visible')
+ })
+ })
+ })
+ describe('about', () => {
+ beforeEach(() => {
+ cy.get('gn-ui-record-form')
+ .children('div')
+ .first()
+ .children('div')
+ .eq(1)
+ .as('aboutSection')
+ })
+ describe('unique identifier', () => {
+ it('shows the unique identifier', () => {
+ cy.get('@aboutSection')
+ .find('gn-ui-form-field')
+ .eq(0)
+ .find('gn-ui-form-field-simple')
+ .find('input')
+ .invoke('val')
+ .then((val) => {
+ cy.get('@recordUuid').should('eq', val)
+ })
+ })
+ })
+ describe('resource updated', () => {
+ beforeEach(() => {
+ cy.get('@aboutSection')
+ .find('gn-ui-form-field-date-updated')
+ .eq(0)
+ .as('resourceUpdatedField')
+ })
+ it('shows the resource update date', () => {
+ cy.get('@resourceUpdatedField')
+ .find('input')
+ .invoke('val')
+ .should('eq', '11/1/2019')
+ })
+ it('edits and saves the resource update date', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('@resourceUpdatedField')
+ .find('input')
+ .type('{selectall}{del}01/01/2019{enter}')
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@resourceUpdatedField')
+ .find('input')
+ .invoke('val')
+ .should('eq', '1/1/2019')
+ })
+ })
+ describe('update frequency', () => {
+ describe('when the regularity switch is on', () => {
+ it('should allow to select a frequency', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-update-frequency')
+ .find('gn-ui-check-toggle label')
+ .click()
+ cy.get('gn-ui-form-field-update-frequency')
+ .find('gn-ui-dropdown-selector')
+ .openDropdown()
+ .children('button')
+ .eq(3)
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-update-frequency')
+ .find('gn-ui-dropdown-selector')
+ .getActiveDropdownOption()
+ .find('span')
+ .invoke('text')
+ .should('eq', ' 2 times per week ')
+ })
+ })
+ describe('when the regularity switch is off', () => {
+ it('should show default frequency', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-update-frequency')
+ .find('gn-ui-check-toggle label')
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-update-frequency')
+ .find('gn-ui-dropdown-selector')
+ .find('button')
+ .find('div')
+ .invoke('text')
+ .should('eq', ' Once per day ')
+ })
+ })
+ })
+ describe('temporal extents', () => {
+ it('should show the two extents buttons', () => {
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('gn-ui-button')
+ .should('have.length', 2)
+ })
+ it('adds a time instant', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('gn-ui-button')
+ .first()
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('gn-ui-date-picker')
+ .should('be.visible')
+ })
+ it('adds a time period', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('gn-ui-button')
+ .eq(1)
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('gn-ui-date-range-picker')
+ .should('be.visible')
+ })
+ it('should delete dates', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('[data-test="remove-item"]')
+ .first()
+ .click()
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('[data-test="remove-item"]')
+ .first()
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('[data-test="remove-item"]')
+ .should('have.length', 0)
+ })
+ describe('date picker in sortable list', () => {
+ it('should keep the date picker open when selecting the start date of a range', () => {
+ cy.get('gn-ui-form-field-temporal-extents gn-ui-button')
+ .eq(1)
+ .click()
+ cy.get('gn-ui-form-field-temporal-extents')
+ .find('[data-cy=date-picker-button]')
+ .click()
+ cy.get('mat-calendar').contains('1').click()
+ cy.get('mat-calendar').should('be.visible')
+ })
+ })
+ })
+ })
+ describe('geographical coverage', () => {
+ it('should show a map', () => {
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-map-container')
+ .should('be.visible')
+ })
+ describe('spatial extents', () => {
+ it('should display place keywords', () => {
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-autocomplete')
+ .should('have.length', 1)
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-badge')
+ .should('have.length', 1)
+ })
+ it('should allow to delete and add a place keywords', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-badge')
+ .find('gn-ui-button')
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-badge')
+ .should('have.length', 0)
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-autocomplete')
+ .click()
+ cy.get('mat-option').eq(1).click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('gn-ui-form-field-spatial-extent')
+ .find('gn-ui-badge')
+ .should('have.length', 1)
+ })
+ })
+ describe('spatial scope', () => {
+ it('toggle between national and regional spatial coverage', () => {
+ cy.get('gn-ui-switch-toggle').should('exist')
+
+ cy.get('gn-ui-switch-toggle').find('mat-button-toggle').eq(0).click()
+ cy.get('mat-button-toggle')
+ .eq(0)
+ .should('have.class', 'mat-button-toggle-checked')
+ cy.get('mat-button-toggle')
+ .eq(1)
+ .should('not.have.class', 'mat-button-toggle-checked')
+
+ cy.get('gn-ui-switch-toggle').find('mat-button-toggle').eq(1).click()
+ cy.get('mat-button-toggle')
+ .eq(1)
+ .should('have.class', 'mat-button-toggle-checked')
+ cy.get('mat-button-toggle')
+ .eq(0)
+ .should('not.have.class', 'mat-button-toggle-checked')
+ })
+ })
+ })
+ describe('distribution resources', () => {
+ beforeEach(() => {
+ cy.get('@resourcePageBtn').click()
+ })
+ it('adds a resource', () => {
+ // item count before adding
+ cy.get(
+ 'gn-ui-form-field-online-resources gn-ui-online-resource-card'
+ ).should('have.length', 0)
+ cy.editor_wrapPreviousDraft()
+ // add a service distribution
+ cy.get('[data-cy="online-resources-type"] button').eq(1).click()
+ cy.get('gn-ui-online-service-resource-input mat-radio-button')
+ .contains('WMS')
+ .click()
+ cy.get('gn-ui-online-service-resource-input')
+ .find('[data-cy="identifier-in-service"]')
+ .type('A layer name as identifier in service')
+ cy.get('gn-ui-form-field-online-resources')
+ .find('gn-ui-url-input')
+ .find('input')
+ .type('http://example.com/wms')
+ cy.get('gn-ui-form-field-online-resources')
+ .find('gn-ui-url-input')
+ .find('button')
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@resourcePageBtn').click()
+ cy.get(
+ 'gn-ui-form-field-online-resources gn-ui-online-resource-card'
+ ).should('have.length', 1)
+ })
+ it('modifies a resource', () => {
+ cy.get('gn-ui-form-field-online-resources gn-ui-online-resource-card')
+ .eq(0)
+ .as('wmsService')
+ cy.get('@wmsService')
+ .find('[data-test=card-title]')
+ .invoke('text')
+ .invoke('trim')
+ .should('eql', 'A layer name as identifier in service')
+ cy.editor_wrapPreviousDraft()
+ // open modify dialog
+ cy.get('@wmsService').find('button[data-test=card-modify]').click()
+ cy.get(
+ 'gn-ui-modal-dialog [data-cy="identifier-in-service"] input'
+ ).clear()
+ cy.get(
+ 'gn-ui-modal-dialog [data-cy="identifier-in-service"] input'
+ ).type('{selectAll}{backspace}new identifier')
+ cy.get('gn-ui-modal-dialog [data-cy=confirm-button]').click()
+ cy.editor_publishAndReload()
+ cy.get('@resourcePageBtn').click()
+ cy.get('@wmsService')
+ .find('[data-test=card-title]')
+ .invoke('text')
+ .invoke('trim')
+ .should('eql', 'new identifier')
+ cy.get('@wmsService').scrollIntoView()
+ cy.screenshot({ capture: 'viewport' })
+ })
+ it('deletes a resource', () => {
+ // item count before deleting
+ cy.get(
+ 'gn-ui-form-field-online-resources gn-ui-online-resource-card'
+ ).should('have.length', 1)
+ cy.editor_wrapPreviousDraft()
+ // delete the first item
+ cy.get(
+ 'gn-ui-form-field-online-resources gn-ui-sortable-list [data-test=remove-item]'
+ )
+ .eq(0)
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@resourcePageBtn').click()
+ cy.get(
+ 'gn-ui-form-field-online-resources gn-ui-online-resource-card'
+ ).should('have.length', 0)
+ })
+ })
+ describe('attached resources', () => {
+ beforeEach(() => {
+ cy.get('@resourcePageBtn').click()
+ })
+ it('adds a resource', () => {
+ // item count before adding
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-online-resource-card'
+ ).should('have.length', 1)
+ cy.editor_wrapPreviousDraft()
+ // upload readme file
+ cy.get('gn-ui-form-field-online-link-resources label').selectFile(
+ 'src/fixtures/readme.txt'
+ )
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@resourcePageBtn').click()
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-online-resource-card'
+ ).should('have.length', 2)
+ })
+ it('modifies a resource', () => {
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-online-resource-card'
+ )
+ .eq(1)
+ .as('readmeLink')
+ cy.get('@readmeLink')
+ .find('[data-test=card-title]')
+ .invoke('text')
+ .invoke('trim')
+ .should('eql', 'readme.txt')
+ cy.editor_wrapPreviousDraft()
+ // open modify dialog
+ cy.get('@readmeLink').find('button[data-test=card-modify]').click()
+ cy.get('gn-ui-modal-dialog gn-ui-text-input')
+ .find('input')
+ .type('{selectall}{del}new title!')
+ cy.get('gn-ui-modal-dialog gn-ui-text-area')
+ .find('textarea')
+ .type('new description')
+ cy.get('gn-ui-modal-dialog [data-cy=confirm-button]').click()
+ cy.editor_publishAndReload()
+ cy.get('@resourcePageBtn').click()
+ cy.get('@readmeLink')
+ .find('[data-test=card-title]')
+ .invoke('text')
+ .invoke('trim')
+ .should('eql', 'new title!')
+ cy.get('@readmeLink').scrollIntoView()
+ cy.screenshot({ capture: 'viewport' })
+ })
+ it('deletes a resource', () => {
+ // item count before deleting
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-online-resource-card'
+ ).should('have.length', 2)
+ cy.editor_wrapPreviousDraft()
+ // delete the second item
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-sortable-list [data-test=remove-item]'
+ )
+ .eq(1)
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@resourcePageBtn').click()
+ cy.get(
+ 'gn-ui-form-field-online-link-resources gn-ui-online-resource-card'
+ ).should('have.length', 1)
+ })
+ })
+ describe('classification', () => {
+ beforeEach(() => {
+ cy.get('@accessContactPageBtn').click()
+ })
+ describe('keywords', () => {
+ it('should show the current keywords', () => {
+ cy.get('gn-ui-form-field-keywords')
+ .find('gn-ui-badge')
+ .should('have.length', 41)
+ })
+ it('should add a keyword', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-keywords').find('gn-ui-autocomplete').click()
+ cy.get('mat-option').first().click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@accessContactPageBtn').click()
+ cy.get('gn-ui-form-field-keywords')
+ .find('gn-ui-badge')
+ .should('have.length', 42)
+ .last()
+ .find('span')
+ .should('have.text', 'Addresses ')
+ })
+ it('should delete a keyword', () => {
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-keywords')
+ .find('gn-ui-badge')
+ .last()
+ .find('gn-ui-button')
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@accessContactPageBtn').click()
+ cy.get('gn-ui-form-field-keywords')
+ .find('gn-ui-badge')
+ .should('have.length', 41)
+ })
+ })
+
+ describe('data managers', () => {
+ describe('contacts for resources', () => {
+ beforeEach(() => {
+ cy.get('@accessContactPageBtn').click()
+ })
+
+ it('show the contacts for resource of the dataset', () => {
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .should('have.length', 3)
+ })
+
+ it('delete a contact for resource', () => {
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .find('gn-ui-contact-card')
+ .should('have.length', 3)
+ cy.editor_wrapPreviousDraft()
+
+ // delete 2 out of 3 contacts
+ cy.get('[data-test=displayedRoles] [data-test=remove-item]')
+ .first()
+ .click()
+ cy.get('[data-test=displayedRoles] [data-test=remove-item]')
+ .first()
+ .click()
+
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@accessContactPageBtn').click()
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .find('gn-ui-contact-card')
+ .should('have.length', 1)
+ })
+
+ it('show the roles available to add', () => {
+ cy.get('[data-test=rolesToPick]')
+ .children()
+ .should('have.length', 4)
+ })
+
+ it('click on a role adds it to the list of displayed role', () => {
+ cy.get('[data-test="rolesToPick"]').children().eq(2).click()
+ cy.get('[data-test=rolesToPick]')
+ .children()
+ .should('have.length', 3)
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .should('have.length', 2)
+ })
+
+ it('add a contact for resource', () => {
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .find('gn-ui-contact-card')
+ .should('have.length', 1)
+ cy.editor_wrapPreviousDraft()
+
+ cy.get('[data-test=displayedRoles]')
+ .find('gn-ui-autocomplete')
+ .type('bar')
+
+ cy.get('mat-option')
+ .should('have.text', ' Barbara Roberts (Barbie Inc.) ')
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@accessContactPageBtn').click()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .find('gn-ui-contact-card')
+ .should('have.length', 2)
+ })
+ })
+ })
+ })
+ describe('Access and constraints', () => {
+ describe('Open data switch', () => {
+ beforeEach(() => {
+ cy.get('@accessContactPageBtn').click()
+ })
+ describe('When the open data switch is unchecked', () => {
+ it('should display the licence form field', () => {
+ cy.get('gn-ui-form-field-license').should('be.visible')
+ cy.get('gn-ui-form-field-license')
+ .find('button')
+ .children('div')
+ .first()
+ .invoke('text')
+ .should('eq', ' Creative Commons CC-BY ')
+ })
+ })
+ describe('When the open data switch is checked', () => {
+ it('should not display the licence form field', () => {
+ cy.get('[data-cy="openDataToggle"]').click()
+ cy.get('gn-ui-form-field-license').should('not.exist')
+ })
+ })
+ })
+ describe('licenses', () => {
+ beforeEach(() => {
+ cy.get('@accessContactPageBtn').click()
+ })
+ it('should select a new license and show it on reload', () => {
+ cy.get('gn-ui-form-field-license')
+ .find('button')
+ .children('div')
+ .first()
+ .invoke('text')
+ .should('eq', ' Creative Commons CC-BY ')
+ cy.editor_wrapPreviousDraft()
+ cy.get('gn-ui-form-field-license')
+ .find('gn-ui-dropdown-selector')
+ .openDropdown()
+ .children('button')
+ .eq(2)
+ .click()
+ cy.editor_publishAndReload()
+ cy.get('@saveStatus').should('eq', 'record_up_to_date')
+ cy.get('@accessContactPageBtn').click()
+ cy.get('gn-ui-form-field-license')
+ .find('button')
+ .children('div')
+ .first()
+ .invoke('text')
+ .should('eq', ' Creative Commons CC-0 ')
+ })
+ })
+ })
+ })
+})
diff --git a/apps/metadata-editor-e2e/src/e2e/home.cy.ts b/apps/metadata-editor-e2e/src/e2e/home.cy.ts
deleted file mode 100644
index f9da3186b5..0000000000
--- a/apps/metadata-editor-e2e/src/e2e/home.cy.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-const fakeUser = {
- username: 'user',
- password: 'Abcde12!',
- email: 'jitewaboh@lagify.com',
- name: 'Homer',
- surname: 'Simpson',
-}
-
-const gnBaseUrl = 'http://localhost:8080/geonetwork/srv/eng/'
-
-describe('avatar', () => {
- describe('display avatar for user without gravatar hash', () => {
- it('should display placeholder url', () => {
- cy.login('admin', 'admin', false)
- cy.visit(`${gnBaseUrl}admin.console#/organization`)
- cy.get('#gn-btn-user-add').click()
- cy.get('#username').type(fakeUser.username)
- cy.get('#gn-user-password').type(fakeUser.password)
- cy.get('#gn-user-password2').type(fakeUser.password)
- cy.get('[name="name"]').type(fakeUser.name)
- cy.get('[name="surname"]').type(fakeUser.surname)
- cy.get('[name="email"]').type(fakeUser.email)
- cy.get('.fa-save').click()
-
- cy.visit(`${gnBaseUrl}admin.console#/settings`)
- cy.get('[id="system/users/identicon"]').type('{selectAll}{del}')
- cy.get('#gn-btn-settings-save').click()
-
- cy.visit('/')
- cy.get('gn-ui-avatar')
- .children('img')
- .should('have.attr', 'src')
- .should('eq', 'https://www.gravatar.com/avatar/?d=mp')
- })
- it('should display monsterid', () => {
- cy.login('admin', 'admin', false)
- cy.visit(`${gnBaseUrl}admin.console#/settings`)
- cy.get('[id="system/users/identicon"]').type(
- '{selectAll}gravatar:monsterid'
- )
- cy.get('#gn-btn-settings-save').click()
-
- cy.visit('/')
- cy.get('gn-ui-avatar')
- .children('img')
- .should('have.attr', 'src')
- .should('eq', 'https://www.gravatar.com/avatar/?d=monsterid')
- })
- })
- describe('display avatar for user with hash', () => {
- it('should display the correct profile picture', () => {
- cy.login(fakeUser.username, fakeUser.password, true)
- cy.get('gn-ui-avatar')
- .children('img')
- .should('have.attr', 'src')
- .should(
- 'eq',
- 'https://www.gravatar.com/avatar/09abd59eb5653a7183ba812b8261f48b?d=monsterid'
- )
- })
- })
-})
diff --git a/apps/metadata-editor-e2e/src/e2e/import.cy.ts b/apps/metadata-editor-e2e/src/e2e/import.cy.ts
new file mode 100644
index 0000000000..0c73348dc2
--- /dev/null
+++ b/apps/metadata-editor-e2e/src/e2e/import.cy.ts
@@ -0,0 +1,79 @@
+// eslint-disable-next-line @nx/enforce-module-boundaries
+import { simpleDatasetRecordAsXmlFixture } from '@geonetwork-ui/common/fixtures'
+
+describe('import', () => {
+ beforeEach(() => {
+ cy.login('admin', 'admin', false)
+ cy.visit('/catalog/search')
+ })
+
+ describe('import a record', () => {
+ beforeEach(() => {
+ // Open the import overlay
+ cy.get('[data-test="import-record"]').click()
+ })
+
+ it('should show the import menu overlay', () => {
+ cy.get('gn-ui-import-record').should('be.visible')
+ cy.get('[data-test="importMenuMainSection"]').should('be.visible')
+ })
+
+ describe('import by URL section', () => {
+ beforeEach(() => {
+ cy.get('[data-test="importFromUrlButton"]').click()
+ })
+
+ it('should show the import by URL section', () => {
+ cy.get('[data-test="importMenuImportExternalFileSection"]').should(
+ 'be.visible'
+ )
+ })
+
+ it('should show the import by URL section', () => {
+ cy.get('[data-test="importMenuImportExternalFileSection"]').should(
+ 'be.visible'
+ )
+ })
+
+ it('should import a record', () => {
+ cy.get('[data-test="importMenuImportExternalFileSection"]')
+ .find('gn-ui-url-input')
+ .type('http://www.marvelous-record/xml/download')
+
+ cy.intercept(
+ {
+ method: 'GET',
+ url: /\/xml\/download$/,
+ },
+ {
+ statusCode: 200,
+ body: simpleDatasetRecordAsXmlFixture(),
+ }
+ ).as('importUrlRequest')
+
+ cy.get('gn-ui-url-input').find('gn-ui-button').find('button').click()
+
+ // Check that the record is correctly displayed
+ cy.get('gn-ui-record-form').should('be.visible')
+
+ cy.get('gn-ui-record-form')
+ .find('gn-ui-form-field')
+ .eq(0)
+ .find('input')
+ .invoke('val')
+ .should('contain', 'Copy')
+ })
+
+ it('should be able to navigate back to the main section', () => {
+ cy.get(
+ '[data-test="importMenuImportExternalFileSectionBackButton"]'
+ ).click()
+
+ cy.get('[data-test="importMenuMainSection"]').should('be.visible')
+ cy.get('[data-test="importMenuImportExternalFileSection"]').should(
+ 'not.exist'
+ )
+ })
+ })
+ })
+})
diff --git a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts b/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts
deleted file mode 100644
index 14ddf94cf9..0000000000
--- a/apps/metadata-editor-e2e/src/e2e/my-org.cy.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-describe('my-org', () => {
- beforeEach(() => {
- cy.login('barbie', 'p4ssworD_', false)
- cy.intercept({
- method: 'GET',
- url: '/geonetwork/srv/api/userselections/0/101',
- }).as('dataGetFirst')
- cy.visit(`/catalog/my-org`)
- cy.wait('@dataGetFirst').its('response.statusCode').should('equal', 200)
- })
- describe('my-org display', () => {
- it('should show my-org name and logo', () => {
- cy.get('h1').should('not.have.text', '')
- cy.get('gn-ui-thumbnail')
- })
- it('should show the user and record count', () => {
- cy.get('[data-cy=link-to-datahub]').should('not.have.text', '')
- cy.get('[data-cy=link-to-users]').should('not.have.text', '')
- })
- it('should show my-org records', () => {
- cy.get('gn-ui-interactive-table .contents').should('have.length.above', 1)
- })
- })
- describe('routing', () => {
- it('should access the datahub with a filter', () => {
- cy.get('[data-cy=link-to-datahub]')
- .should('have.attr', 'href')
- .then((href) => {
- expect(href).to.include('search?publisher=Barbie+Inc')
- })
- })
- it('should access the user list page and show my-org users', () => {
- cy.visit(`/catalog/my-org`)
- cy.get('[data-cy=link-to-users]').click()
- cy.url().should('include', '/users/my-org')
- cy.get('gn-ui-interactive-table .contents').should('have.length.above', 1)
- cy.get('h1').should('not.have.text', '')
- cy.get('gn-ui-thumbnail')
- })
- })
-})
diff --git a/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts b/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts
new file mode 100644
index 0000000000..20afc25802
--- /dev/null
+++ b/apps/metadata-editor-e2e/src/e2e/record-actions.cy.ts
@@ -0,0 +1,214 @@
+describe('record-actions', () => {
+ beforeEach(() => {
+ cy.login('admin', 'admin', false)
+ cy.visit('/catalog/search')
+ })
+ describe('delete', () => {
+ const recordId = `TEST_RECORD_${Date.now()}`
+ describe('record with draft', () => {
+ it('should delete the record, delete its associated draft and refresh the interface', () => {
+ // First create a record and its draft
+ cy.get('[data-cy="create-record"]').click()
+ cy.url().should('include', '/create')
+ cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea')
+ .as('abstractField')
+ .focus()
+ cy.get('@abstractField').type('record abstract')
+ cy.get('[data-test="recordTitleInput"]').click()
+ cy.get('[data-test="recordTitleInput"]').type('{selectAll}{backspace}')
+ cy.get('[data-test="recordTitleInput"]').type(recordId)
+ cy.intercept({
+ method: 'PUT',
+ pathname: '**/records',
+ }).as('insertRecord')
+ cy.get('md-editor-publish-button').click()
+ cy.wait('@insertRecord')
+ cy.get('@abstractField').type('draft abstract')
+ // Assert that the draft exists in the local storage
+ cy.editor_readFormUniqueIdentifier().then((uniqueIdentifier) =>
+ cy
+ .window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${uniqueIdentifier}`)
+ .should('exist')
+ )
+ cy.visit('/my-space/my-records')
+ cy.get('[data-cy="table-row"]')
+ .contains(recordId)
+ .should('have.length', 1)
+ cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '1')
+ // Delete the record
+ cy.get('[data-test="record-menu-button"]').last().click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ cy.get('[data-cy="table-row"]')
+ .contains(recordId)
+ .should('have.length', 0)
+ cy.get('gn-ui-notification').should('contain', 'Delete success')
+ })
+ })
+
+ describe('draft without record', () => {
+ it('should delete the draft and refresh the interface', () => {
+ // First create a draft
+ cy.get('[data-cy="create-record"]').click()
+ cy.url().should('include', '/create')
+ cy.get('[data-test="recordTitleInput"]').click()
+ cy.get('[data-test="recordTitleInput"]').type('{selectAll}{backspace}')
+ cy.get('[data-test="recordTitleInput"]').type(recordId)
+ cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea')
+ .as('abstractField')
+ .focus()
+ cy.get('@abstractField').type('draft abstract')
+ cy.editor_readFormUniqueIdentifier().then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('contain', 'draft abstract')
+ })
+ cy.visit('/my-space/my-draft')
+ cy.get('[data-cy="table-row"]')
+ .contains(recordId)
+ .should('have.length', 1)
+ cy.get('[data-cy="dashboard-drafts-count"]').should('contain', '1')
+ // Delete the draft
+ cy.get('[data-test="record-menu-button"]').click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ cy.get('[data-cy="table-row"]').should('not.exist')
+ })
+ })
+ })
+
+ describe('create', () => {
+ beforeEach(() => {
+ // create a record
+ cy.get('[data-cy="create-record"]').click()
+ cy.url().should('include', '/create')
+ })
+
+ afterEach(() => {
+ // delete the new record
+ cy.visit('/catalog/search')
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('[data-test="record-menu-button"]').first().click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ })
+
+ it('should create the record without error', () => {
+ // Check that the record is correctly displayed
+ cy.get('gn-ui-record-form').should('be.visible')
+
+ cy.get('gn-ui-record-form')
+ .children()
+ .eq(0)
+ .children()
+ .should('have.length', 3)
+
+ cy.get('[data-test="previousNextPageButtons"]')
+ .children()
+ .eq(0)
+ .should('contain.text', 'Come back later')
+ cy.get('[data-test="previousNextPageButtons"]')
+ .children()
+ .eq(1)
+ .should('contain.text', 'Next')
+ })
+
+ it('back navigation should go to search after creating a record', () => {
+ cy.go('back')
+ cy.url().should('include', '/catalog/search')
+ })
+
+ it('the created record should have the registered user as point of contact in the data managers section', () => {
+ cy.get('[data-test=pageSelectorButtons]')
+ .find('gn-ui-button')
+ .eq(2)
+ .click()
+
+ cy.get('[data-test=displayedRoles]').children().should('have.length', 1)
+
+ cy.get('[data-test=displayedRoles]')
+ .children()
+ .find('gn-ui-contact-card')
+ .get('[data-test=contactCardName]')
+ .invoke('text')
+ .should('contain', 'admin admin')
+ })
+ })
+
+ describe('undo', () => {
+ it('should restore the record and refresh the interface', () => {
+ // First create a record and its draft
+ cy.get('[data-cy="create-record"]').click()
+ cy.url().should('include', '/create')
+ cy.get('gn-ui-form-field[ng-reflect-model=abstract] textarea')
+ .as('abstractField')
+ .focus()
+ cy.get('@abstractField').type('record abstract')
+ cy.editor_readFormUniqueIdentifier().then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('exist')
+ })
+
+ cy.intercept({
+ method: 'PUT',
+ pathname: '**/records',
+ }).as('insertRecord')
+ cy.get('md-editor-publish-button').click()
+ cy.wait('@insertRecord')
+ cy.get('[data-cy="undo-button"] button').should('be.disabled')
+
+ cy.get('@abstractField').clear()
+ cy.get('@abstractField').focus()
+ cy.get('@abstractField').type('draft abstract')
+ cy.editor_readFormUniqueIdentifier().then((recordUuid) => {
+ cy.window()
+ .its('localStorage')
+ .invoke('getItem', `geonetwork-ui-draft-${recordUuid}`)
+ .should('contain', 'draft abstract')
+ })
+
+ cy.get('[data-cy="undo-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ cy.get('@abstractField').should('have.value', 'record abstract')
+
+ // delete the new record
+ cy.visit('/catalog/search')
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('[data-test="record-menu-button"]').first().click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ })
+ })
+
+ describe('duplicate', () => {
+ it('should duplicate the record', () => {
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('[data-cy="table-row"]')
+ .first()
+ .find('[data-test="record-menu-button"]')
+ .click()
+ cy.get('[data-test="record-menu-duplicate-button"]').click()
+
+ cy.get('gn-ui-form-field')
+ .first()
+ .find('input')
+ .invoke('val')
+ .should('eq', 'Accroches vélos MEL (Copy)')
+
+ // delete the new record
+ cy.visit('/catalog/search')
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('.table-header-cell').eq(1).click()
+ cy.get('[data-test="record-menu-button"]').first().click()
+ cy.get('[data-test="record-menu-delete-button"]').click()
+ cy.get('[data-cy="confirm-button"]').click()
+ })
+ })
+})
diff --git a/apps/metadata-editor-e2e/src/fixtures/readme.txt b/apps/metadata-editor-e2e/src/fixtures/readme.txt
new file mode 100644
index 0000000000..375f2c6d08
--- /dev/null
+++ b/apps/metadata-editor-e2e/src/fixtures/readme.txt
@@ -0,0 +1 @@
+This is a sample file that will be attached to a resource for demonstration purposes.
diff --git a/apps/metadata-editor-e2e/src/fixtures/sample.png b/apps/metadata-editor-e2e/src/fixtures/sample.png
new file mode 100644
index 0000000000..3a06761435
Binary files /dev/null and b/apps/metadata-editor-e2e/src/fixtures/sample.png differ
diff --git a/apps/metadata-editor/project.json b/apps/metadata-editor/project.json
index 4d451d4600..8227bc39fc 100644
--- a/apps/metadata-editor/project.json
+++ b/apps/metadata-editor/project.json
@@ -36,6 +36,7 @@
"styles": [
"tailwind.base.css",
"apps/metadata-editor/src/styles.css",
+ "node_modules/ol/ol.css",
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
],
"scripts": []
diff --git a/apps/metadata-editor/src/app/app.module.ts b/apps/metadata-editor/src/app/app.module.ts
index db79237697..c098de62f3 100644
--- a/apps/metadata-editor/src/app/app.module.ts
+++ b/apps/metadata-editor/src/app/app.module.ts
@@ -24,7 +24,11 @@ import { provideAnimations } from '@angular/platform-browser/animations'
import { extModules } from './build-specifics'
import { DashboardPageComponent } from './dashboard/dashboard-page.component'
import { EditorRouterService } from './router.service'
-import { provideGn4, provideRepositoryUrl } from '@geonetwork-ui/api/repository'
+import {
+ LOGIN_URL,
+ provideGn4,
+ provideRepositoryUrl,
+} from '@geonetwork-ui/api/repository'
import { FeatureEditorModule } from '@geonetwork-ui/feature/editor'
@NgModule({
@@ -62,6 +66,10 @@ import { FeatureEditorModule } from '@geonetwork-ui/feature/editor'
importProvidersFrom(EffectsModule.forRoot()),
provideGn4(),
provideAnimations(),
+ {
+ provide: LOGIN_URL,
+ useFactory: () => getGlobalConfig().LOGIN_URL,
+ },
],
bootstrap: [AppComponent],
})
diff --git a/apps/metadata-editor/src/app/app.routes.ts b/apps/metadata-editor/src/app/app.routes.ts
index 878df29ce9..a4096ae9af 100644
--- a/apps/metadata-editor/src/app/app.routes.ts
+++ b/apps/metadata-editor/src/app/app.routes.ts
@@ -1,109 +1,118 @@
import { Route } from '@angular/router'
import { DashboardPageComponent } from './dashboard/dashboard-page.component'
-import { SignInPageComponent } from './sign-in/sign-in-page.component'
import { EditPageComponent } from './edit/edit-page.component'
import { EditRecordResolver } from './edit-record.resolver'
-import { MyRecordsComponent } from './records/my-records/my-records.component'
import { MyDraftComponent } from './records/my-draft/my-draft.component'
-import { MyLibraryComponent } from './records/my-library/my-library.component'
-import { SearchRecordsComponent } from './records/search-records/search-records-list.component'
+import { TemplatesComponent } from './records/templates/templates.component'
import { MyOrgUsersComponent } from './my-org-users/my-org-users.component'
-import { MyOrgRecordsComponent } from './records/my-org-records/my-org-records.component'
import { NewRecordResolver } from './new-record.resolver'
+import { DuplicateRecordResolver } from './duplicate-record.resolver'
+import { AllRecordsComponent } from './records/all-records/all-records.component'
+import { MyRecordsStateWrapperComponent } from './records/my-records/my-records-state-wrapper.component'
+import { AuthGuardService } from './auth-guard.service'
export const appRoutes: Route[] = [
- { path: '', redirectTo: 'catalog/search', pathMatch: 'prefix' },
{
- path: 'catalog',
- component: DashboardPageComponent,
- outlet: 'primary',
+ path: '',
+ canActivate: [AuthGuardService],
children: [
{
path: '',
- redirectTo: 'search',
+ redirectTo: 'catalog/search',
pathMatch: 'prefix',
},
{
- path: 'discussion',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
+ path: 'catalog',
+ component: DashboardPageComponent,
+ outlet: 'primary',
+ children: [
+ {
+ path: '',
+ redirectTo: 'search',
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'discussion',
+ component: AllRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'calendar',
+ component: AllRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'contacts',
+ component: AllRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'thesaurus',
+ component: AllRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'search',
+ title: 'Search Records',
+ component: AllRecordsComponent,
+ pathMatch: 'prefix',
+ },
+ ],
},
{
- path: 'calendar',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
+ path: 'my-space',
+ component: DashboardPageComponent,
+ outlet: 'primary',
+ title: 'My space',
+ children: [
+ {
+ path: 'my-records',
+ title: 'My Records',
+ component: MyRecordsStateWrapperComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'my-draft',
+ title: 'My Draft',
+ component: MyDraftComponent,
+ pathMatch: 'prefix',
+ },
+ {
+ path: 'templates',
+ title: 'Templates',
+ component: TemplatesComponent,
+ pathMatch: 'prefix',
+ },
+ ],
},
{
- path: 'contacts',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
+ path: 'users',
+ component: DashboardPageComponent,
+ outlet: 'primary',
+ title: 'Users',
+ children: [
+ {
+ path: 'my-org',
+ component: MyOrgUsersComponent,
+ pathMatch: 'prefix',
+ },
+ ],
},
{
- path: 'thesaurus',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
+ path: 'create',
+ component: EditPageComponent,
+ resolve: { record: NewRecordResolver },
},
{
- path: 'search',
- title: 'Search Records',
- component: SearchRecordsComponent,
- pathMatch: 'prefix',
+ path: 'duplicate/:uuid',
+ component: EditPageComponent,
+ resolve: { record: DuplicateRecordResolver },
},
{
- path: 'my-org',
- title: 'My Organisation',
- component: MyOrgRecordsComponent,
+ path: 'edit/:uuid',
+ component: EditPageComponent,
+ resolve: { record: EditRecordResolver },
},
],
},
- {
- path: 'my-space',
- component: DashboardPageComponent,
- outlet: 'primary',
- title: 'My space',
- children: [
- {
- path: 'my-records',
- title: 'My Records',
- component: MyRecordsComponent,
- pathMatch: 'prefix',
- },
- {
- path: 'my-draft',
- title: 'My Draft',
- component: MyDraftComponent,
- pathMatch: 'prefix',
- },
- {
- path: 'templates',
- title: 'Templates',
- component: MyLibraryComponent,
- pathMatch: 'prefix',
- },
- ],
- },
- {
- path: 'users',
- component: DashboardPageComponent,
- outlet: 'primary',
- title: 'Users',
- children: [
- {
- path: 'my-org',
- component: MyOrgUsersComponent,
- pathMatch: 'prefix',
- },
- ],
- },
- { path: 'sign-in', component: SignInPageComponent },
- {
- path: 'create',
- component: EditPageComponent,
- resolve: { record: NewRecordResolver },
- },
- {
- path: 'edit/:uuid',
- component: EditPageComponent,
- resolve: { record: EditRecordResolver },
- },
]
diff --git a/apps/metadata-editor/src/app/auth-guard.service.spec.ts b/apps/metadata-editor/src/app/auth-guard.service.spec.ts
new file mode 100644
index 0000000000..8966947d11
--- /dev/null
+++ b/apps/metadata-editor/src/app/auth-guard.service.spec.ts
@@ -0,0 +1,44 @@
+import { AuthGuardService } from './auth-guard.service'
+import { MockBuilder, MockProvider } from 'ng-mocks'
+import { AuthService } from '@geonetwork-ui/api/repository'
+import { TestBed } from '@angular/core/testing'
+import { of } from 'rxjs'
+import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface'
+
+Object.defineProperty(window, 'location', {
+ value: new URL('http://localhost'),
+})
+
+describe('AuthGuardService', () => {
+ let service: AuthGuardService
+ beforeEach(() => {
+ return MockBuilder(AuthGuardService)
+ })
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ MockProvider(AuthService, {
+ loginUrl: 'http://login.com/bla?',
+ }),
+ MockProvider(PlatformServiceInterface, {
+ isAnonymous: () => of(true),
+ }),
+ ],
+ })
+ window.location.href = 'http://original.path'
+ service = TestBed.inject(AuthGuardService)
+ })
+
+ it('returns true if the user is logged in', async () => {
+ jest
+ .spyOn(TestBed.inject(PlatformServiceInterface), 'isAnonymous')
+ .mockReturnValue(of(false))
+ expect(await service.canActivate()).toBe(true)
+ expect(window.location.href).toBe('http://original.path/')
+ })
+ it('redirects the user to the login page if the user is not logged in', async () => {
+ expect(await service.canActivate()).toBe(false)
+ expect(window.location.href).toBe('http://login.com/bla?')
+ })
+})
diff --git a/apps/metadata-editor/src/app/auth-guard.service.ts b/apps/metadata-editor/src/app/auth-guard.service.ts
new file mode 100644
index 0000000000..fa7087f610
--- /dev/null
+++ b/apps/metadata-editor/src/app/auth-guard.service.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core'
+import { AuthService } from '@geonetwork-ui/api/repository'
+import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface'
+import { firstValueFrom } from 'rxjs'
+
+@Injectable({ providedIn: 'root' })
+export class AuthGuardService {
+ constructor(
+ private platformService: PlatformServiceInterface,
+ private authService: AuthService
+ ) {}
+
+ // this will redirect the user to the authentication form if required
+ async canActivate(): Promise {
+ const notLoggedIn = await firstValueFrom(this.platformService.isAnonymous())
+ if (notLoggedIn) {
+ window.location.href = this.authService.loginUrl
+ return false
+ }
+ return true
+ }
+}
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
index ed67fe8b72..425d9d2591 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.html
@@ -11,37 +11,41 @@
dashboard.catalog.allRecords