Skip to content

Commit

Permalink
feat(me): Add contacts for resource field
Browse files Browse the repository at this point in the history
  • Loading branch information
Romuald Caplier committed Jul 31, 2024
1 parent e397bff commit 8209dfa
Show file tree
Hide file tree
Showing 31 changed files with 936 additions and 165 deletions.
85 changes: 85 additions & 0 deletions apps/metadata-editor-e2e/src/e2e/edit.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ describe('editor form', () => {
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('resourceDescriptionPageSelectorButton')
cy.get('[data-test=pageSelectorButtons]')
.find('gn-ui-button')
.eq(1)
.as('resourcePageSelectorButton')
cy.get('[data-test=pageSelectorButtons]')
.find('gn-ui-button')
.eq(2)
.as('accessAndContactPageSelectorButton')
})

it('form shows correctly', () => {
Expand Down Expand Up @@ -57,4 +69,77 @@ describe('editor form', () => {
})
cy.get('md-editor-publish-button').click()
})

describe('record fields', () => {
describe('contacts for resources', () => {
beforeEach(() => {
cy.login('admin', 'admin', false)

// Alpine convention record
cy.visit('/edit/accroche_velos')

cy.get('@accessAndContactPageSelectorButton').click()
})

it('show the contacts for resource of the dataset', () => {
cy.get('[data-test=displayedRoles]').children().should('have.length', 1)
})

it('show the 5 roles available to add', () => {
cy.get('[data-test=rolesToPick]').children().should('have.length', 5)
})

it('click on a role adds it to the list of displayed role', () => {
cy.get('[data-test="rolesToPick"]')

Check failure on line 93 in apps/metadata-editor-e2e/src/e2e/edit.cy.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line
.children()
.eq(2)
.click()
.invoke('text')

cy.get('[data-test=rolesToPick]').children().should('have.length', 4)

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.get('[data-test=displayedRoles]')

Check failure on line 110 in apps/metadata-editor-e2e/src/e2e/edit.cy.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line
.find('gn-ui-autocomplete')
.type('bar')
.then((test) => {
console.log(test)
cy.get('mat-option')

Check failure on line 115 in apps/metadata-editor-e2e/src/e2e/edit.cy.ts

View workflow job for this annotation

GitHub Actions / Format check, lint, unit tests

It is unsafe to chain further commands that rely on the subject after this command. It is best to split the chain, chaining again from `cy.` in a next command line
.should('have.text', ' Barbara Roberts ')
.click()
.then(() => {
cy.get('[data-test=displayedRoles]')
.children()
.find('gn-ui-contact-card')
.should('have.length', 2)
})
})
})

it('delete a contact for resource', () => {
cy.get('[data-test=displayedRoles]')
.children()
.find('gn-ui-contact-card')
.should('have.length', 1)

cy.get('[data-test=displayedRoles]')
.children()
.get('[data-test=removeContactButton]')
.click()

cy.get('[data-test=displayedRoles]')
.children()
.find('gn-ui-contact-card')
.should('not.exist')
})
})
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<div class="w-full flex flex-row p-8 items-center gap-6">
<div
class="w-full flex flex-row p-8 items-center gap-6"
data-test="pageSelectorButtons"
>
<ng-container
*ngFor="let page of pages$ | async; let index = index; let isLast = last"
>
Expand Down
34 changes: 34 additions & 0 deletions libs/common/domain/src/lib/model/record/contact.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Organization } from './organization.model'
import { marker } from '@biesbjerg/ngx-translate-extract-marker'

export const RoleValues = [
'unspecified',
Expand All @@ -24,6 +25,39 @@ export const RoleValues = [
'user', // Party who uses the resource
]

export const RoleLabels = new Map<string, string>([
['unspecified', marker('metadata.contactForResource.role.unspecified')],
['other', marker('metadata.contactForResource.role.other')],
['author', marker('metadata.contactForResource.role.author')],
['collaborator', marker('metadata.contactForResource.role.collaborator')],
['contributor', marker('metadata.contactForResource.role.contributor')],
['custodian', marker('metadata.contactForResource.role.custodian')],
['distributor', marker('metadata.contactForResource.role.distributor')],
['editor', marker('metadata.contactForResource.role.editor')],
['funder', marker('metadata.contactForResource.role.funder')],
['mediator', marker('metadata.contactForResource.role.mediator')],
['originator', marker('metadata.contactForResource.role.originator')],
['owner', marker('metadata.contactForResource.role.owner')],
[
'point_of_contact',
marker('metadata.contactForResource.role.point_of_contact'),
],
[
'principal_investigator',
marker('metadata.contactForResource.role.principal_investigator'),
],
['processor', marker('metadata.contactForResource.role.processor')],
['publisher', marker('metadata.contactForResource.role.publisher')],
[
'resource_provider',
marker('metadata.contactForResource.role.resource_provider'),
],
['rights_holder', marker('metadata.contactForResource.role.rights_holder')],
['sponsor', marker('metadata.contactForResource.role.sponsor')],
['stakeholder', marker('metadata.contactForResource.role.stakeholder')],
['user', marker('metadata.contactForResource.role.user')],
])

export type Role = typeof RoleValues[number]

export interface Individual {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
>{{ contact.firstName }} {{ contact.lastName }}</span
>
<gn-ui-button
type="secondary"
data-test="removeContactButton"
type="light"
extraClass="w-[20px] h-[20px] flex items-center justify-center"
(buttonClick)="removeContact(contact)"
><span class="text-[14px] leading-[0] font-bold">&#x2715;</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ContactCardComponent } from './contact-card.component'
import {
Individual,
Organization,
} from '@geonetwork-ui/common/domain/model/record'
import { MatIconModule } from '@angular/material/icon'
import { CommonModule } from '@angular/common'
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'

describe('ContactCardComponent', () => {
let component: ContactCardComponent
let fixture: ComponentFixture<ContactCardComponent>

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
CommonModule,
MatIconModule,
ButtonComponent,
ContactCardComponent,
],
}).compileComponents()
})

beforeEach(() => {
fixture = TestBed.createComponent(ContactCardComponent)
component = fixture.componentInstance
fixture.detectChanges()
})

it('should create the component', () => {
expect(component).toBeTruthy()
})

it('should have a defined contact input', () => {
const mockContact: Individual = {
firstName: 'John',
lastName: 'Doe',
organization: { name: 'Org1' } as Organization,
email: '[email protected]',
role: 'admin',
address: '',
phone: '',
position: '',
}
component.contact = mockContact
fixture.detectChanges()
expect(component.contact).toEqual(mockContact)
})

it('should have a defined organization input', () => {
const mockOrganization: Organization = {
name: 'Org1',
}
component.organization = mockOrganization
fixture.detectChanges()
expect(component.organization).toEqual(mockOrganization)
})

it('should emit contactRemoved event with the correct contact', () => {
const mockContact: Individual = {
firstName: 'John',
lastName: 'Doe',
organization: { name: 'Org1' } as Organization,
email: '[email protected]',
role: 'admin',
address: '',
phone: '',
position: '',
}
component.contact = mockContact

const contactRemovedSpy = jest.spyOn(component.contactRemoved, 'emit')
component.removeContact(mockContact)
expect(contactRemovedSpy).toHaveBeenCalledWith(mockContact)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
applicationConfig,
componentWrapperDecorator,
Meta,
moduleMetadata,
StoryObj,
} from '@storybook/angular'
import { ContactCardComponent } from './contact-card.component'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { importProvidersFrom } from '@angular/core'
import { MatIconModule } from '@angular/material/icon'
import { CommonModule } from '@angular/common'
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'

export default {
title: 'Elements/ContactCardComponent',
component: ContactCardComponent,
decorators: [
moduleMetadata({
imports: [
CommonModule,
MatIconModule,
ButtonComponent,
ContactCardComponent,
],
}),
applicationConfig({
providers: [importProvidersFrom(BrowserAnimationsModule)],
}),
componentWrapperDecorator(
(story) => `<div style="max-width: 400px; margin: auto;">${story}</div>`
),
],
} as Meta<ContactCardComponent>

export const Primary: StoryObj<ContactCardComponent> = {
args: {
contact: {
firstName: 'John',
lastName: 'Doe',
organization: {
name: 'Example Organization',
},
email: '[email protected]',
role: 'Developer',
address: '123 Main St',
phone: '123-456-7890',
position: 'Senior Developer',
},
organization: {
name: 'Example Organization',
},
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import { CommonModule } from '@angular/common'
import { ButtonComponent } from '@geonetwork-ui/ui/inputs'

@Component({
selector: 'gn-ui-editor-contact-card',
templateUrl: './editor-contact-card.component.html',
styleUrls: ['./editor-contact-card.component.css'],
selector: 'gn-ui-contact-card',
templateUrl: './contact-card.component.html',
styleUrls: ['./contact-card.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, MatIconModule, ButtonComponent],
})
export class EditorContactCardComponent {
export class ContactCardComponent {
@Input() contact: Individual
@Input() organization: Organization
@Output() contactRemoved = new EventEmitter<Individual>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div class="flex flex-col gap-3">
<div class="flex flex-row flex-wrap gap-2" data-test="rolesToPick">
<ng-container *ngFor="let role of rolesToPick">
<gn-ui-button
extraClass="px-2 py-1.5"
(buttonClick)="addRoleToDisplay(role)"
>
<div class="flex flex-row gap-1 items-center">
<span class="text-primary text-[20px] leading-[0] font-bold pb-[5px]"
>&#8330;</span
>
<span class="font-bold" translate>{{ roleToLabel(role) }}</span>
</div>
</gn-ui-button>
</ng-container>
</div>
<div
class="mt-8"
*ngIf="rolesToDisplay && rolesToDisplay.length > 0"
data-test="displayedRoles"
>
<div
*ngFor="let role of rolesToDisplay; let index = index; let isLast = last"
class="flex flex-col gap-4"
>
<div class="flex flex-row justify-between">
<span class="font-bold text-base" translate>{{
roleToLabel(role)
}}</span>
</div>

<gn-ui-autocomplete
[placeholder]="'Choose a contact'"
[action]="autoCompleteAction"
(itemSelected)="addContact($event, role)"
[displayWithFn]="displayWithFn"
[minCharacterCount]="1"
[clearOnSelection]="true"
>
</gn-ui-autocomplete>

<ng-container *ngFor="let contact of getContactsByRole(role)">
<gn-ui-contact-card
[contact]="contact"
[organization]="getOrganizationByName(contact.organization.name)"
(contactRemoved)="removeContact(index)"
></gn-ui-contact-card>
</ng-container>
<hr class="border-t-[#D6D3D1] mt-4 mb-6" *ngIf="!isLast" />
</div>
</div>
</div>
Loading

0 comments on commit 8209dfa

Please sign in to comment.