Skip to content

Commit e8da53d

Browse files
committed
PD-5538
1 parent c203984 commit e8da53d

6 files changed

Lines changed: 269 additions & 92 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
Affiliation,
3+
AffiliationUIGroup,
4+
} from 'src/app/types/record-affiliation.endpoint'
5+
6+
export function getFeaturedEmploymentCaption(
7+
affiliations?: AffiliationUIGroup[]
8+
): string {
9+
if (!affiliations || affiliations.length === 0) {
10+
return ''
11+
}
12+
13+
const employmentGroup = affiliations.find((group) => group.type === 'EMPLOYMENT')
14+
15+
if (!employmentGroup || !employmentGroup.affiliationGroup) {
16+
return ''
17+
}
18+
19+
for (const group of employmentGroup.affiliationGroup) {
20+
if (!group.affiliations) {
21+
continue
22+
}
23+
24+
const featuredAffiliation = group.affiliations.find(
25+
(affiliation) =>
26+
affiliation.featured === true &&
27+
affiliation.affiliationType?.value === 'employment'
28+
)
29+
30+
if (featuredAffiliation) {
31+
return formatAffiliationCaption(featuredAffiliation)
32+
}
33+
}
34+
35+
return ''
36+
}
37+
38+
function formatAffiliationCaption(affiliation: Affiliation): string {
39+
const parts: string[] = []
40+
41+
const orgName = affiliation.affiliationName?.value
42+
if (orgName) {
43+
parts.push(orgName)
44+
}
45+
46+
const locationParts: string[] = []
47+
if (affiliation.city?.value) {
48+
locationParts.push(affiliation.city.value)
49+
}
50+
if (affiliation.region?.value) {
51+
locationParts.push(affiliation.region.value)
52+
}
53+
if (affiliation.countryForDisplay) {
54+
locationParts.push(affiliation.countryForDisplay)
55+
} else if (affiliation.country?.value) {
56+
locationParts.push(affiliation.country.value)
57+
}
58+
59+
if (orgName && locationParts.length > 0) {
60+
parts[0] = `${orgName}: ${locationParts.join(', ')}`
61+
} else if (locationParts.length > 0) {
62+
parts.push(locationParts.join(', '))
63+
}
64+
65+
const roleParts: string[] = []
66+
if (affiliation.roleTitle?.value) {
67+
roleParts.push(affiliation.roleTitle.value)
68+
}
69+
if (affiliation.departmentName?.value) {
70+
roleParts.push(affiliation.departmentName.value)
71+
}
72+
73+
if (roleParts.length > 0) {
74+
if (parts.length > 0) {
75+
parts.push(`- ${roleParts.join(', ')}`)
76+
} else {
77+
parts.push(roleParts.join(', '))
78+
}
79+
}
80+
81+
return parts.join(' ')
82+
}

src/app/record/components/record-header/record-header.component.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,25 @@ import { getUserRecord } from 'src/app/core/record/record.service.spec'
1515
import { getUserSession } from 'src/app/core/user/user.service.spec'
1616
import { NoopAnimationsModule } from '@angular/platform-browser/animations'
1717
import { RumJourneyEventService } from 'src/app/rum/service/customEvent.service'
18+
import { TogglzFlag } from 'src/app/types/config.endpoint'
19+
import { AffiliationType } from 'src/app/types/record-affiliation.endpoint'
1820

1921
describe('RecordHeaderComponent', () => {
2022
let component: RecordHeaderComponent
2123
let fixture: ComponentFixture<RecordHeaderComponent>
2224
let state: RecordHeaderStateService
2325
let recordService: jasmine.SpyObj<RecordService>
26+
let togglzService: { getStateOf: jasmine.Spy }
2427

2528
beforeEach(async () => {
2629
recordService = jasmine.createSpyObj<RecordService>('RecordService', [
2730
'getRecord',
2831
])
32+
togglzService = {
33+
getStateOf: jasmine.createSpy('getStateOf').and.callFake((flag: string) =>
34+
of(flag === TogglzFlag.FEATURED_AFFILIATIONS)
35+
),
36+
}
2937

3038
await TestBed.configureTestingModule({
3139
imports: [
@@ -48,7 +56,7 @@ describe('RecordHeaderComponent', () => {
4856
},
4957
{
5058
provide: TogglzService,
51-
useValue: { getStateOf: () => of(false) },
59+
useValue: togglzService,
5260
},
5361
{
5462
provide: UserService,
@@ -112,4 +120,24 @@ describe('RecordHeaderComponent', () => {
112120
expect(component.bannerTitle).toBe('Published Name')
113121
expect(component.bannerCaption).toBe('')
114122
})
123+
124+
it('should render the featured employment caption from shared state', () => {
125+
const userRecord = getUserRecord()
126+
const featuredAffiliation =
127+
userRecord.affiliations[0].affiliationGroup[0].affiliations[0]
128+
129+
featuredAffiliation.featured = true
130+
featuredAffiliation.affiliationType = { value: AffiliationType.employment }
131+
featuredAffiliation.roleTitle = { value: 'Engineer' }
132+
featuredAffiliation.departmentName = { value: 'Platform' }
133+
134+
state.setIsPublicRecord(userRecord.userInfo.REAL_USER_ORCID)
135+
state.setLoadingRecordHeader(false)
136+
state.setUserRecord(userRecord)
137+
fixture.detectChanges()
138+
139+
expect(component.bannerCaption).toBe(
140+
'ORCID: city, region, country - Engineer, Platform'
141+
)
142+
})
115143
})

src/app/record/components/record-header/record-header.component.ts

Lines changed: 2 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,9 @@ import { MatTooltipModule } from '@angular/material/tooltip'
2727
import { MatButtonModule } from '@angular/material/button'
2828
import { TogglzService } from 'src/app/core/togglz/togglz.service'
2929
import { TogglzFlag } from 'src/app/types/config.endpoint'
30-
import {
31-
AffiliationUIGroup,
32-
Affiliation,
33-
} from 'src/app/types/record-affiliation.endpoint'
3430
import { RumJourneyEventService } from 'src/app/rum/service/customEvent.service'
3531
import { AppEventName } from 'src/app/rum/app-event-names'
32+
import { getFeaturedEmploymentCaption } from './featured-employment-caption.util'
3633

3734
@Component({
3835
selector: 'app-record-header',
@@ -382,90 +379,6 @@ export class RecordHeaderComponent implements OnInit, OnDestroy {
382379
this.bannerCaption = ''
383380
return
384381
}
385-
this.bannerCaption = this.formatFeaturedEmployment(userRecord?.affiliations)
386-
}
387-
388-
private formatFeaturedEmployment(
389-
affiliations?: AffiliationUIGroup[]
390-
): string {
391-
if (!affiliations || affiliations.length === 0) {
392-
return ''
393-
}
394-
395-
// Find the EMPLOYMENT group
396-
const employmentGroup = affiliations.find(
397-
(group) => group.type === 'EMPLOYMENT'
398-
)
399-
400-
if (!employmentGroup || !employmentGroup.affiliationGroup) {
401-
return ''
402-
}
403-
404-
// Find featured employment in all affiliation groups
405-
for (const group of employmentGroup.affiliationGroup) {
406-
if (group.affiliations) {
407-
const featuredAffiliation = group.affiliations.find(
408-
(affiliation) =>
409-
affiliation.featured === true &&
410-
affiliation.affiliationType?.value === 'employment'
411-
)
412-
413-
if (featuredAffiliation) {
414-
return this.formatAffiliationCaption(featuredAffiliation)
415-
}
416-
}
417-
}
418-
419-
return ''
420-
}
421-
422-
private formatAffiliationCaption(affiliation: Affiliation): string {
423-
const parts: string[] = []
424-
425-
// Organization name
426-
const orgName = affiliation.affiliationName?.value
427-
if (orgName) {
428-
parts.push(orgName)
429-
}
430-
431-
// Location: city, region, country
432-
const locationParts: string[] = []
433-
if (affiliation.city?.value) {
434-
locationParts.push(affiliation.city.value)
435-
}
436-
if (affiliation.region?.value) {
437-
locationParts.push(affiliation.region.value)
438-
}
439-
if (affiliation.countryForDisplay) {
440-
locationParts.push(affiliation.countryForDisplay)
441-
} else if (affiliation.country?.value) {
442-
locationParts.push(affiliation.country.value)
443-
}
444-
445-
// Combine organization and location
446-
if (orgName && locationParts.length > 0) {
447-
parts[0] = `${orgName}: ${locationParts.join(', ')}`
448-
} else if (locationParts.length > 0) {
449-
parts.push(locationParts.join(', '))
450-
}
451-
452-
// Role and department
453-
const roleParts: string[] = []
454-
if (affiliation.roleTitle?.value) {
455-
roleParts.push(affiliation.roleTitle.value)
456-
}
457-
if (affiliation.departmentName?.value) {
458-
roleParts.push(affiliation.departmentName.value)
459-
}
460-
461-
if (roleParts.length > 0) {
462-
if (parts.length > 0) {
463-
parts.push(`- ${roleParts.join(', ')}`)
464-
} else {
465-
parts.push(roleParts.join(', '))
466-
}
467-
}
468-
469-
return parts.join(' ')
382+
this.bannerCaption = getFeaturedEmploymentCaption(userRecord?.affiliations)
470383
}
471384
}

src/app/record/pages/my-orcid/my-orcid-header-loading.spec.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import { RecordHeaderStateService } from 'src/app/core/record-header-state/recor
33
import { getUserRecord } from 'src/app/core/record/record.service.spec'
44
import { UserRecord } from 'src/app/types/record.local'
55
import { MyOrcidComponent } from './my-orcid.component'
6+
import { RumJourneyEventService } from 'src/app/rum/service/customEvent.service'
7+
import { AppEventName } from 'src/app/rum/app-event-names'
8+
import { AffiliationType } from 'src/app/types/record-affiliation.endpoint'
69

710
describe('MyOrcidComponent header loading state', () => {
811
let component: MyOrcidComponent
912
let recordHeaderState: RecordHeaderStateService
13+
let rumEvents: jasmine.SpyObj<RumJourneyEventService>
1014

1115
beforeEach(() => {
1216
recordHeaderState = new RecordHeaderStateService()
17+
rumEvents = jasmine.createSpyObj<RumJourneyEventService>('RumJourneyEventService', [
18+
'recordSimpleEvent',
19+
])
1320
component = new MyOrcidComponent(
1421
{} as any,
1522
{} as any,
@@ -27,8 +34,11 @@ describe('MyOrcidComponent header loading state', () => {
2734
{} as any,
2835
{} as any,
2936
{} as any,
30-
recordHeaderState
37+
recordHeaderState,
38+
rumEvents
3139
)
40+
component.platform = { columns12: true } as any
41+
;(component as any).headerRumStartTime = 100
3242
})
3343

3444
it('should mark the record header ready before the full record is loaded', (done) => {
@@ -52,4 +62,84 @@ describe('MyOrcidComponent header loading state', () => {
5262
done()
5363
})
5464
})
65+
66+
it('should record the public header ready event once with elapsed time', () => {
67+
const partialRecord = {
68+
...getUserRecord(),
69+
works: undefined,
70+
fundings: undefined,
71+
peerReviews: undefined,
72+
researchResources: undefined,
73+
} as UserRecord
74+
75+
component.publicOrcid = partialRecord.userInfo.REAL_USER_ORCID
76+
spyOn(performance, 'now').and.returnValue(250)
77+
78+
component.checkRecordHeaderLoadingState(partialRecord)
79+
component.checkRecordHeaderLoadingState(partialRecord)
80+
81+
expect(rumEvents.recordSimpleEvent).toHaveBeenCalledTimes(1)
82+
expect(rumEvents.recordSimpleEvent).toHaveBeenCalledWith(
83+
AppEventName.RecordHeaderPublicReady,
84+
jasmine.objectContaining({
85+
elapsed_ms: 150,
86+
has_record_issues: false,
87+
header_variant: 'public',
88+
is_desktop: true,
89+
})
90+
)
91+
})
92+
93+
it('should not record the public header ready event for non-public records', () => {
94+
spyOn(performance, 'now').and.returnValue(250)
95+
96+
component.checkRecordHeaderLoadingState(getUserRecord())
97+
98+
expect(rumEvents.recordSimpleEvent).not.toHaveBeenCalledWith(
99+
AppEventName.RecordHeaderPublicReady,
100+
jasmine.anything()
101+
)
102+
})
103+
104+
it('should record the featured employment caption event once when enabled', () => {
105+
const record = getUserRecord()
106+
record.affiliations[0].affiliationGroup[0].affiliations[0].featured = true
107+
record.affiliations[0].affiliationGroup[0].affiliations[0].affiliationType = {
108+
value: AffiliationType.employment,
109+
}
110+
111+
component.publicOrcid = record.userInfo.REAL_USER_ORCID
112+
;(component as any).featuredAffiliationsEnabled = true
113+
spyOn(performance, 'now').and.returnValue(275)
114+
115+
;(component as any).checkFeaturedEmploymentCaptionState(record)
116+
;(component as any).checkFeaturedEmploymentCaptionState(record)
117+
118+
expect(rumEvents.recordSimpleEvent).toHaveBeenCalledTimes(1)
119+
expect(rumEvents.recordSimpleEvent).toHaveBeenCalledWith(
120+
AppEventName.RecordHeaderFeaturedEmploymentCaptionLoaded,
121+
jasmine.objectContaining({
122+
caption_type: 'featured_employment',
123+
elapsed_ms: 175,
124+
has_record_issues: false,
125+
header_variant: 'public',
126+
is_desktop: true,
127+
})
128+
)
129+
})
130+
131+
it('should not record the featured employment caption event when disabled', () => {
132+
const record = getUserRecord()
133+
record.affiliations[0].affiliationGroup[0].affiliations[0].featured = true
134+
135+
component.publicOrcid = record.userInfo.REAL_USER_ORCID
136+
;(component as any).featuredAffiliationsEnabled = false
137+
138+
;(component as any).checkFeaturedEmploymentCaptionState(record)
139+
140+
expect(rumEvents.recordSimpleEvent).not.toHaveBeenCalledWith(
141+
AppEventName.RecordHeaderFeaturedEmploymentCaptionLoaded,
142+
jasmine.anything()
143+
)
144+
})
55145
})

0 commit comments

Comments
 (0)