Skip to content

Commit 1acb996

Browse files
committed
feat(metadata): replaced crossref with ror
1 parent 3141ef7 commit 1acb996

File tree

16 files changed

+171
-64
lines changed

16 files changed

+171
-64
lines changed

src/app/core/interceptors/auth.interceptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const authInterceptor: HttpInterceptorFn = (
1313

1414
const csrfToken = cookieService.get('api-csrf');
1515

16-
if (!req.url.includes('/api.crossref.org/funders')) {
16+
if (!req.url.includes('/api.ror')) {
1717
const headers: Record<string, string> = {};
1818

1919
headers['Accept'] = req.responseType === 'text' ? '*/*' : 'application/vnd.api+json;version=2.20';

src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
<p-select
1111
[id]="'funderName-' + $index"
1212
formControlName="funderName"
13-
[options]="funderOptions()"
14-
optionLabel="label"
15-
optionValue="value"
13+
[options]="fundersList()"
14+
optionLabel="name"
15+
optionValue="name"
1616
[placeholder]="'project.metadata.funding.dialog.selectFunder' | translate"
1717
class="w-full"
1818
[filter]="true"
19-
filterBy="label"
19+
filterBy="name"
2020
[showClear]="true"
2121
[loading]="fundersLoading()"
2222
[emptyFilterMessage]="filterMessage() | translate"

src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.spec.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
55
import { DestroyRef } from '@angular/core';
66
import { ComponentFixture, TestBed } from '@angular/core/testing';
77

8+
import { RorFunderOption } from '../../models/ror.model';
89
import { MetadataSelectors } from '../../store';
910

1011
import { FundingDialogComponent } from './funding-dialog.component';
1112

12-
import { MOCK_FUNDERS } from '@testing/mocks/funder.mock';
1313
import { OSFTestingModule } from '@testing/osf.testing.module';
1414
import { provideMockStore } from '@testing/providers/store-provider.mock';
1515

16+
const MOCK_ROR_FUNDERS: RorFunderOption[] = [{ id: 'https://ror.org/0test', name: 'Test Funder' }];
17+
1618
describe('FundingDialogComponent', () => {
1719
let component: FundingDialogComponent;
1820
let fixture: ComponentFixture<FundingDialogComponent>;
@@ -25,7 +27,7 @@ describe('FundingDialogComponent', () => {
2527
MockProvider(DynamicDialogConfig, { data: { funders: [] } }),
2628
provideMockStore({
2729
signals: [
28-
{ selector: MetadataSelectors.getFundersList, value: MOCK_FUNDERS },
30+
{ selector: MetadataSelectors.getFundersList, value: MOCK_ROR_FUNDERS },
2931
{ selector: MetadataSelectors.getFundersLoading, value: false },
3032
],
3133
}),
@@ -145,6 +147,16 @@ describe('FundingDialogComponent', () => {
145147
expect(entry.get('funderIdentifierType')?.value).toBe(initialValues.funderIdentifierType);
146148
});
147149

150+
it('should update funding entry when funder is selected from ROR list', () => {
151+
const entry = component.fundingEntries.at(0);
152+
153+
component.onFunderSelected('Test Funder', 0);
154+
155+
expect(entry.get('funderName')?.value).toBe('Test Funder');
156+
expect(entry.get('funderIdentifier')?.value).toBe('https://ror.org/0test');
157+
expect(entry.get('funderIdentifierType')?.value).toBe('ROR ID');
158+
});
159+
148160
it('should remove funding entry when more than one exists', () => {
149161
component.addFundingEntry();
150162
expect(component.fundingEntries.length).toBe(2);

src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ export class FundingDialogComponent implements OnInit {
3333

3434
fundersList = select(MetadataSelectors.getFundersList);
3535
fundersLoading = select(MetadataSelectors.getFundersLoading);
36-
funderOptions = computed(() => {
37-
const funders = this.fundersList() || [];
38-
return funders.map((funder) => ({
39-
label: funder.name,
40-
value: funder.name,
41-
id: funder.id,
42-
uri: funder.uri,
43-
}));
44-
});
4536

4637
fundingForm = new FormGroup<FundingForm>({ fundingEntries: new FormArray<FormGroup<FundingEntryForm>>([]) });
4738

@@ -132,8 +123,8 @@ export class FundingDialogComponent implements OnInit {
132123
const entry = this.fundingEntries.at(index);
133124
entry.patchValue({
134125
funderName: selectedFunder.name,
135-
funderIdentifier: selectedFunder.uri,
136-
funderIdentifierType: 'Crossref Funder ID',
126+
funderIdentifier: selectedFunder.id,
127+
funderIdentifierType: 'ROR',
137128
});
138129
}
139130
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './cedar-records.mapper';
22
export * from './metadata.mapper';
3+
export * from './ror.mapper';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { RorFunderOption, RorOrganization, RorSearchResponse } from '../models/ror.model';
2+
3+
export class RorMapper {
4+
static toFunderOptions(response: RorSearchResponse): RorFunderOption[] {
5+
return response.items.map((org) => ({
6+
id: org.id,
7+
name: this.getRorDisplayName(org),
8+
}));
9+
}
10+
11+
static getRorDisplayName(org: RorOrganization): string {
12+
const rorDisplay = org.names?.find((n) => n.types?.includes('ror_display'));
13+
if (rorDisplay?.value) return rorDisplay.value;
14+
const label = org.names?.find((n) => n.types?.includes('label'));
15+
if (label?.value) return label.value;
16+
if (org.names?.length && org.names[0].value) return org.names[0].value;
17+
return org.id ?? '';
18+
}
19+
}

src/app/features/metadata/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './funding-dialog.model';
55
export * from './metadata.model';
66
export * from './metadata-json-api.model';
77
export * from './resource-information-form.model';
8+
export * from './ror.model';

src/app/features/metadata/models/metadata.model.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,3 @@ export interface Funder {
4040
awardUri: string;
4141
awardTitle: string;
4242
}
43-
44-
export interface CrossRefFundersResponse {
45-
status: string;
46-
'message-type': string;
47-
'message-version': string;
48-
message: CrossRefFundersMessage;
49-
}
50-
51-
export interface CrossRefFundersMessage {
52-
'items-per-page': number;
53-
query: CrossRefQuery;
54-
'total-results': number;
55-
items: CrossRefFunder[];
56-
}
57-
58-
export interface CrossRefQuery {
59-
'start-index': number;
60-
'search-terms': string | null;
61-
}
62-
63-
export interface CrossRefFunder {
64-
id: string;
65-
location: string;
66-
name: string;
67-
'alt-names': string[];
68-
uri: string;
69-
replaces: string[];
70-
'replaced-by': string[];
71-
tokens: string[];
72-
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
export interface RorAdmin {
2+
created: {
3+
date: string;
4+
schema_version: string;
5+
};
6+
last_modified: {
7+
date: string;
8+
schema_version: string;
9+
};
10+
}
11+
12+
export interface RorExternalId {
13+
all: string[];
14+
preferred: string | null;
15+
type: 'grid' | 'fundref' | 'isni' | 'wikidata';
16+
}
17+
18+
export interface RorLink {
19+
type: 'website' | 'wikipedia';
20+
value: string;
21+
}
22+
23+
export interface RorGeonamesDetails {
24+
continent_code: string;
25+
continent_name: string;
26+
country_code: string;
27+
country_name: string;
28+
country_subdivision_code: string;
29+
country_subdivision_name: string;
30+
lat: number;
31+
lng: number;
32+
name: string;
33+
}
34+
35+
export interface RorLocation {
36+
geonames_details: RorGeonamesDetails;
37+
geonames_id: number;
38+
}
39+
40+
export interface RorName {
41+
lang: string | null;
42+
types: ('ror_display' | 'label' | 'alias' | 'acronym')[];
43+
value: string;
44+
}
45+
46+
export interface RorRelationship {
47+
type: string;
48+
id: string;
49+
label: string;
50+
}
51+
52+
export interface RorOrganization {
53+
id: string;
54+
admin: RorAdmin;
55+
domains: string[];
56+
established: number | null;
57+
external_ids: RorExternalId[];
58+
links: RorLink[];
59+
locations: RorLocation[];
60+
names: RorName[];
61+
relationships: RorRelationship[];
62+
status: 'active' | 'inactive' | 'withdrawn';
63+
types: (
64+
| 'education'
65+
| 'healthcare'
66+
| 'company'
67+
| 'archive'
68+
| 'nonprofit'
69+
| 'government'
70+
| 'facility'
71+
| 'other'
72+
| 'funder'
73+
)[];
74+
}
75+
76+
export interface RorMetaCount {
77+
id: string;
78+
title: string;
79+
count: number;
80+
}
81+
82+
export interface RorMeta {
83+
types: RorMetaCount[];
84+
countries: RorMetaCount[];
85+
continents: RorMetaCount[];
86+
statuses: RorMetaCount[];
87+
}
88+
89+
export interface RorSearchResponse {
90+
items: RorOrganization[];
91+
meta: RorMeta;
92+
number_of_results: number;
93+
time_taken: number;
94+
}
95+
96+
export interface RorFunderOption {
97+
id: string;
98+
name: string;
99+
}
100+
101+
export interface RorDisplayData {
102+
id: string;
103+
displayName: string;
104+
acronym?: string;
105+
type: string;
106+
country: string;
107+
city: string;
108+
established?: number;
109+
website?: string;
110+
status: string;
111+
}

src/app/features/metadata/services/metadata.service.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { LicenseOptions } from '@osf/shared/models/license/license.model';
1010
import { BaseNodeAttributesJsonApi } from '@osf/shared/models/nodes/base-node-attributes-json-api.model';
1111
import { JsonApiService } from '@osf/shared/services/json-api.service';
1212

13-
import { CedarRecordsMapper, MetadataMapper } from '../mappers';
13+
import { CedarRecordsMapper, MetadataMapper, RorMapper } from '../mappers';
1414
import {
1515
CedarMetadataRecord,
1616
CedarMetadataRecordJsonApi,
@@ -21,7 +21,8 @@ import {
2121
MetadataJsonApi,
2222
MetadataJsonApiResponse,
2323
} from '../models';
24-
import { CrossRefFundersResponse, CustomItemMetadataRecord, MetadataModel } from '../models/metadata.model';
24+
import { CustomItemMetadataRecord, MetadataModel } from '../models/metadata.model';
25+
import { RorFunderOption, RorSearchResponse } from '../models/ror.model';
2526

2627
@Injectable({
2728
providedIn: 'root',
@@ -78,14 +79,14 @@ export class MetadataService {
7879
);
7980
}
8081

81-
getFundersList(searchQuery?: string): Observable<CrossRefFundersResponse> {
82-
let url = `${this.funderApiUrl}funders?mailto=support%40osf.io`;
82+
getFundersList(searchQuery?: string): Observable<RorFunderOption[]> {
83+
let url = `${this.funderApiUrl}/organizations?filter=types:funder`;
8384

8485
if (searchQuery && searchQuery.trim()) {
8586
url += `&query=${encodeURIComponent(searchQuery.trim())}`;
8687
}
8788

88-
return this.jsonApiService.get<CrossRefFundersResponse>(url);
89+
return this.jsonApiService.get<RorSearchResponse>(url).pipe(map((response) => RorMapper.toFunderOptions(response)));
8990
}
9091

9192
getMetadataCedarTemplates(url?: string): Observable<CedarMetadataTemplateJsonApi> {

0 commit comments

Comments
 (0)