From 92ee7ed2e8af8462a38a4bc5420a5936b851a172 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 16 Jan 2026 16:49:38 +0200 Subject: [PATCH 1/8] show funder awards for registry submissions --- .../registry-submission-item.component.html | 11 ++++++- .../registry-submission-item.component.ts | 12 +++++++- .../funder-awards-list.component.html | 20 +++++++++++++ .../funder-awards-list.component.scss | 0 .../funder-awards-list.component.spec.ts | 22 ++++++++++++++ .../funder-awards-list.component.ts | 30 +++++++++++++++++++ src/assets/i18n/en.json | 1 + 7 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/app/shared/funder-awards-list/funder-awards-list.component.html create mode 100644 src/app/shared/funder-awards-list/funder-awards-list.component.scss create mode 100644 src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts create mode 100644 src/app/shared/funder-awards-list/funder-awards-list.component.ts diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html index 006ce75fc..acff7eaa5 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html @@ -1,5 +1,5 @@
- +
@@ -71,6 +71,15 @@

{{ submission().title }}

+ + +
+ @if (isOpen()) { +

{{submission().id}}

+ + } +
+
diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts index 770db64d4..aa99fe3d4 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts @@ -4,12 +4,13 @@ import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'pr import { Button } from 'primeng/button'; import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core'; import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component'; import { IconComponent } from '@osf/shared/components/icon/icon.component'; import { TruncatedTextComponent } from '@osf/shared/components/truncated-text/truncated-text.component'; import { DateAgoPipe } from '@osf/shared/pipes/date-ago.pipe'; +import { FunderAwardsListComponent } from '@shared/funder-awards-list/funder-awards-list.component'; import { REGISTRY_ACTION_LABEL, ReviewStatusIcon } from '../../constants'; import { ActionStatus, SubmissionReviewStatus } from '../../enums'; @@ -29,6 +30,7 @@ import { RegistryModeration } from '../../models'; AccordionHeader, AccordionContent, ContributorsListComponent, + FunderAwardsListComponent, ], templateUrl: './registry-submission-item.component.html', styleUrl: './registry-submission-item.component.scss', @@ -39,6 +41,7 @@ export class RegistrySubmissionItemComponent { submission = input.required(); loadContributors = output(); loadMoreContributors = output(); + isOpen = signal(false); selected = output(); @@ -67,6 +70,13 @@ export class RegistrySubmissionItemComponent { }); handleOpen() { + console.log(this.submission().id) + this.isOpen.set(true); this.loadContributors.emit(); } + + handleClose() { + console.log('handleClose'); + this.isOpen.set(false); + } } diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.html b/src/app/shared/funder-awards-list/funder-awards-list.component.html new file mode 100644 index 000000000..84126f64d --- /dev/null +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.html @@ -0,0 +1,20 @@ +
+ @let funders = customItemMetadata()?.funders; + @if (funders?.length) { +

{{ 'resourceCard.labels.funderAwards' | translate }}

+
+ @for (funder of funders; track $index) { + +
{{ funder.funderName }}
+ +
{{ funder.awardNumber }}
+
+ } + +
+ } +
diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.scss b/src/app/shared/funder-awards-list/funder-awards-list.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts b/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts new file mode 100644 index 000000000..4ab0e277c --- /dev/null +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FunderAwardsListComponent } from './funder-awards-list.component'; + +describe('FunderAwardsListComponent', () => { + let component: FunderAwardsListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FunderAwardsListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FunderAwardsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.ts b/src/app/shared/funder-awards-list/funder-awards-list.component.ts new file mode 100644 index 000000000..b7295c8ec --- /dev/null +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.ts @@ -0,0 +1,30 @@ +import { Store } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +import { GetCustomItemMetadata, MetadataSelectors } from '@osf/features/metadata/store'; + +@Component({ + selector: 'osf-funder-awards-list', + imports: [RouterLink, TranslatePipe], + templateUrl: './funder-awards-list.component.html', + styleUrl: './funder-awards-list.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FunderAwardsListComponent { + registryId = input(null); + private store = inject(Store); + customItemMetadata = this.store.selectSignal(MetadataSelectors.getCustomItemMetadata); + + constructor() { + effect(() => { + const registryId = this.registryId(); + if (registryId) { + this.store.dispatch(new GetCustomItemMetadata(registryId)); + } + }); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 13b5ec8f8..3e1af97b6 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -2778,6 +2778,7 @@ "withdrawn": "Withdrawn", "from": "From:", "funder": "Funder:", + "funderAwards": "Funder awards:", "resourceNature": "Resource type:", "dateCreated": "Date created", "dateModified": "Date modified", From cd77a3f8980767e3ac55dd02d1c42a4efbbe8211 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Fri, 16 Jan 2026 16:59:45 +0200 Subject: [PATCH 2/8] fix linter --- .../registry-submission-item.component.html | 2 +- .../registry-submission-item.component.ts | 2 -- .../funder-awards-list.component.html | 25 +++++++++---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html index acff7eaa5..57b0b9404 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html @@ -75,7 +75,7 @@

{{ submission().title }}

@if (isOpen()) { -

{{submission().id}}

+

{{ submission().id }}

}
diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts index aa99fe3d4..22274f2cb 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts @@ -70,13 +70,11 @@ export class RegistrySubmissionItemComponent { }); handleOpen() { - console.log(this.submission().id) this.isOpen.set(true); this.loadContributors.emit(); } handleClose() { - console.log('handleClose'); this.isOpen.set(false); } } diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.html b/src/app/shared/funder-awards-list/funder-awards-list.component.html index 84126f64d..34cbeb506 100644 --- a/src/app/shared/funder-awards-list/funder-awards-list.component.html +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.html @@ -2,19 +2,18 @@ @let funders = customItemMetadata()?.funders; @if (funders?.length) {

{{ 'resourceCard.labels.funderAwards' | translate }}

- } From 02ed6339126f0227c3a85ac2dbe04162cf65f67c Mon Sep 17 00:00:00 2001 From: mkovalua Date: Sat, 17 Jan 2026 19:10:02 +0200 Subject: [PATCH 3/8] make metadata calls shared --- src/app/features/metadata/services/index.ts | 1 - src/app/features/metadata/store/metadata.state.ts | 2 +- .../metadata => shared}/services/metadata.service.ts | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 src/app/features/metadata/services/index.ts rename src/app/{features/metadata => shared}/services/metadata.service.ts (96%) diff --git a/src/app/features/metadata/services/index.ts b/src/app/features/metadata/services/index.ts deleted file mode 100644 index 92c69e450..000000000 --- a/src/app/features/metadata/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './metadata.service'; diff --git a/src/app/features/metadata/store/metadata.state.ts b/src/app/features/metadata/store/metadata.state.ts index 245895fd9..b7172fd61 100644 --- a/src/app/features/metadata/store/metadata.state.ts +++ b/src/app/features/metadata/store/metadata.state.ts @@ -7,7 +7,7 @@ import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@osf/shared/helpers/state-error.handler'; import { CedarMetadataRecord, CedarMetadataRecordJsonApi, MetadataModel } from '../models'; -import { MetadataService } from '../services'; +import { MetadataService } from '@osf/shared/services/metadata.service'; import { AddCedarMetadataRecordToState, diff --git a/src/app/features/metadata/services/metadata.service.ts b/src/app/shared/services/metadata.service.ts similarity index 96% rename from src/app/features/metadata/services/metadata.service.ts rename to src/app/shared/services/metadata.service.ts index 75ae0c86b..3df9186ff 100644 --- a/src/app/features/metadata/services/metadata.service.ts +++ b/src/app/shared/services/metadata.service.ts @@ -10,7 +10,8 @@ import { LicenseOptions } from '@osf/shared/models/license/license.model'; import { BaseNodeAttributesJsonApi } from '@osf/shared/models/nodes/base-node-attributes-json-api.model'; import { JsonApiService } from '@osf/shared/services/json-api.service'; -import { CedarRecordsMapper, MetadataMapper } from '../mappers'; +// import { CedarRecordsMapper, MetadataMapper } from '../mappers'; +import { CedarRecordsMapper, MetadataMapper } from '@osf/features/metadata/mappers'; import { CedarMetadataRecord, CedarMetadataRecordJsonApi, @@ -20,8 +21,8 @@ import { CustomMetadataJsonApiResponse, MetadataJsonApi, MetadataJsonApiResponse, -} from '../models'; -import { CrossRefFundersResponse, CustomItemMetadataRecord, MetadataModel } from '../models/metadata.model'; +} from '@osf/features/metadata/models'; +import { CrossRefFundersResponse, CustomItemMetadataRecord, MetadataModel } from '@osf/features/metadata/models'; @Injectable({ providedIn: 'root', From 479f94f8b91ac0f84560ac4f8c03865f2c7dcfe4 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 19 Jan 2026 14:55:09 +0200 Subject: [PATCH 4/8] format metadata --- .../features/metadata/store/metadata.state.ts | 2 +- src/app/shared/services/metadata.service.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/features/metadata/store/metadata.state.ts b/src/app/features/metadata/store/metadata.state.ts index b7172fd61..af839233a 100644 --- a/src/app/features/metadata/store/metadata.state.ts +++ b/src/app/features/metadata/store/metadata.state.ts @@ -5,9 +5,9 @@ import { catchError, finalize, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@osf/shared/helpers/state-error.handler'; +import { MetadataService } from '@osf/shared/services/metadata.service'; import { CedarMetadataRecord, CedarMetadataRecordJsonApi, MetadataModel } from '../models'; -import { MetadataService } from '@osf/shared/services/metadata.service'; import { AddCedarMetadataRecordToState, diff --git a/src/app/shared/services/metadata.service.ts b/src/app/shared/services/metadata.service.ts index 3df9186ff..b74d82b64 100644 --- a/src/app/shared/services/metadata.service.ts +++ b/src/app/shared/services/metadata.service.ts @@ -4,25 +4,25 @@ import { map } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; import { ENVIRONMENT } from '@core/provider/environment.provider'; -import { ResourceType } from '@osf/shared/enums/resource-type.enum'; -import { IdentifierModel } from '@osf/shared/models/identifiers/identifier.model'; -import { LicenseOptions } from '@osf/shared/models/license/license.model'; -import { BaseNodeAttributesJsonApi } from '@osf/shared/models/nodes/base-node-attributes-json-api.model'; -import { JsonApiService } from '@osf/shared/services/json-api.service'; - -// import { CedarRecordsMapper, MetadataMapper } from '../mappers'; import { CedarRecordsMapper, MetadataMapper } from '@osf/features/metadata/mappers'; import { CedarMetadataRecord, CedarMetadataRecordJsonApi, CedarMetadataTemplateJsonApi, CedarRecordDataBinding, + CrossRefFundersResponse, + CustomItemMetadataRecord, CustomMetadataJsonApi, CustomMetadataJsonApiResponse, MetadataJsonApi, MetadataJsonApiResponse, + MetadataModel, } from '@osf/features/metadata/models'; -import { CrossRefFundersResponse, CustomItemMetadataRecord, MetadataModel } from '@osf/features/metadata/models'; +import { ResourceType } from '@osf/shared/enums/resource-type.enum'; +import { IdentifierModel } from '@osf/shared/models/identifiers/identifier.model'; +import { LicenseOptions } from '@osf/shared/models/license/license.model'; +import { BaseNodeAttributesJsonApi } from '@osf/shared/models/nodes/base-node-attributes-json-api.model'; +import { JsonApiService } from '@osf/shared/services/json-api.service'; @Injectable({ providedIn: 'root', From c7c0899ac85b5224d93c2ceb54eb8bcb514b9a4d Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 19 Jan 2026 15:04:33 +0200 Subject: [PATCH 5/8] reimplementation of registry funder award list --- ...egistry-pending-submissions.component.html | 1 + .../registry-pending-submissions.component.ts | 6 +++ .../registry-submission-item.component.html | 13 +++---- .../registry-submission-item.component.ts | 10 ++--- .../registry-submissions.component.html | 1 + .../registry-submissions.component.ts | 6 +++ .../models/registry-moderation.model.ts | 2 + .../registry-moderation.actions.ts | 6 +++ .../registry-moderation.state.ts | 39 ++++++++++++++++++- .../funder-awards-list.component.html | 5 +-- .../funder-awards-list.component.ts | 18 ++------- 11 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html index 77f120b97..96a28cac1 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html @@ -41,6 +41,7 @@ (selected)="navigateToRegistration(item)" (loadContributors)="loadContributors(item)" (loadMoreContributors)="loadMoreContributors(item)" + (loadRegistrySubmissionFunders)="loadRegistrySubmissionFunders(item)" > } diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts index ac0f00e76..d2591cd4e 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts @@ -26,6 +26,7 @@ import { RegistrySort, SubmissionReviewStatus } from '../../enums'; import { RegistryModeration } from '../../models'; import { GetRegistrySubmissionContributors, + GetRegistrySubmissionFunders, GetRegistrySubmissions, LoadMoreRegistrySubmissionContributors, RegistryModerationSelectors, @@ -63,6 +64,7 @@ export class RegistryPendingSubmissionsComponent implements OnInit { getRegistrySubmissions: GetRegistrySubmissions, getRegistrySubmissionContributors: GetRegistrySubmissionContributors, loadMoreRegistrySubmissionContributors: LoadMoreRegistrySubmissionContributors, + getRegistrySubmissionFunders: GetRegistrySubmissionFunders, }); readonly submissions = select(RegistryModerationSelectors.getRegistrySubmissions); @@ -129,6 +131,10 @@ export class RegistryPendingSubmissionsComponent implements OnInit { this.actions.loadMoreRegistrySubmissionContributors(item.id); } + loadRegistrySubmissionFunders(item: RegistryModeration) { + this.actions.getRegistrySubmissionFunders(item.id); + } + private getStatusFromQueryParams() { const queryParams = this.route.snapshot.queryParams; const statusValues = Object.values(SubmissionReviewStatus); diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html index 57b0b9404..b73981496 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html @@ -1,5 +1,5 @@
- +
@@ -70,14 +70,11 @@

{{ submission().title }}

>
- - -
- @if (isOpen()) { -

{{ submission().id }}

- - } +
diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts index 22274f2cb..f6187acfe 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts @@ -4,7 +4,7 @@ import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'pr import { Button } from 'primeng/button'; import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component'; import { IconComponent } from '@osf/shared/components/icon/icon.component'; @@ -41,7 +41,7 @@ export class RegistrySubmissionItemComponent { submission = input.required(); loadContributors = output(); loadMoreContributors = output(); - isOpen = signal(false); + loadRegistrySubmissionFunders = output(); selected = output(); @@ -70,11 +70,7 @@ export class RegistrySubmissionItemComponent { }); handleOpen() { - this.isOpen.set(true); this.loadContributors.emit(); - } - - handleClose() { - this.isOpen.set(false); + this.loadRegistrySubmissionFunders.emit(); } } diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html index 5066d15f7..38c3afbdd 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html @@ -41,6 +41,7 @@ (selected)="navigateToRegistration(item)" (loadContributors)="loadContributors(item)" (loadMoreContributors)="loadMoreContributors(item)" + (loadRegistrySubmissionFunders)="loadRegistrySubmissionFunders(item)" >
} diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts index e664daacc..468349216 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts @@ -26,6 +26,7 @@ import { RegistrySort, SubmissionReviewStatus } from '../../enums'; import { RegistryModeration } from '../../models'; import { GetRegistrySubmissionContributors, + GetRegistrySubmissionFunders, GetRegistrySubmissions, LoadMoreRegistrySubmissionContributors, RegistryModerationSelectors, @@ -63,6 +64,7 @@ export class RegistrySubmissionsComponent implements OnInit { getRegistrySubmissions: GetRegistrySubmissions, getRegistrySubmissionContributors: GetRegistrySubmissionContributors, loadMoreRegistrySubmissionContributors: LoadMoreRegistrySubmissionContributors, + getRegistrySubmissionFunders: GetRegistrySubmissionFunders, }); readonly submissions = select(RegistryModerationSelectors.getRegistrySubmissions); @@ -129,6 +131,10 @@ export class RegistrySubmissionsComponent implements OnInit { this.actions.loadMoreRegistrySubmissionContributors(item.id); } + loadRegistrySubmissionFunders(item: RegistryModeration) { + this.actions.getRegistrySubmissionFunders(item.id); + } + private getStatusFromQueryParams() { const queryParams = this.route.snapshot.queryParams; const statusValues = Object.values(SubmissionReviewStatus); diff --git a/src/app/features/moderation/models/registry-moderation.model.ts b/src/app/features/moderation/models/registry-moderation.model.ts index 2d59b2681..890bce389 100644 --- a/src/app/features/moderation/models/registry-moderation.model.ts +++ b/src/app/features/moderation/models/registry-moderation.model.ts @@ -1,3 +1,4 @@ +import { Funder } from '@osf/features/metadata/models'; import { RegistrationReviewStates } from '@osf/shared/enums/registration-review-states.enum'; import { RevisionReviewStates } from '@osf/shared/enums/revision-review-states.enum'; import { ContributorModel } from '@shared/models/contributors/contributor.model'; @@ -18,4 +19,5 @@ export interface RegistryModeration { contributors?: ContributorModel[]; totalContributors?: number; contributorsPage?: number; + funders?: Funder[]; } diff --git a/src/app/features/moderation/store/registry-moderation/registry-moderation.actions.ts b/src/app/features/moderation/store/registry-moderation/registry-moderation.actions.ts index 3e350142c..eafd2f856 100644 --- a/src/app/features/moderation/store/registry-moderation/registry-moderation.actions.ts +++ b/src/app/features/moderation/store/registry-moderation/registry-moderation.actions.ts @@ -27,3 +27,9 @@ export class LoadMoreRegistrySubmissionContributors { constructor(public registryId: string) {} } + +export class GetRegistrySubmissionFunders { + static readonly type = `${ACTION_SCOPE} Get Registry Submission Funders`; + + constructor(public registryId: string) {} +} diff --git a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts index 52b068e89..5145fe66c 100644 --- a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts +++ b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts @@ -7,6 +7,7 @@ import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@osf/shared/helpers/state-error.handler'; import { PaginatedData } from '@osf/shared/models/paginated-data.model'; +import { MetadataService } from '@osf/shared/services/metadata.service'; import { DEFAULT_TABLE_PARAMS } from '@shared/constants/default-table-params.constants'; import { ResourceType } from '@shared/enums/resource-type.enum'; import { ContributorsService } from '@shared/services/contributors.service'; @@ -16,6 +17,7 @@ import { RegistryModerationService } from '../../services'; import { GetRegistrySubmissionContributors, + GetRegistrySubmissionFunders, GetRegistrySubmissions, LoadMoreRegistrySubmissionContributors, } from './registry-moderation.actions'; @@ -29,7 +31,7 @@ import { REGISTRY_MODERATION_STATE_DEFAULTS, RegistryModerationStateModel } from export class RegistryModerationState { private readonly registryModerationService = inject(RegistryModerationService); private readonly contributorsService = inject(ContributorsService); - + private readonly metadataService = inject(MetadataService); @Action(GetRegistrySubmissionContributors) getRegistrySubmissionContributors( ctx: StateContext, @@ -151,4 +153,39 @@ export class RegistryModerationState { catchError((error) => handleSectionError(ctx, 'submissions', error)) ); } + + @Action(GetRegistrySubmissionFunders) + getRegistrySubmissionFunders( + ctx: StateContext, + { registryId }: GetRegistrySubmissionFunders + ) { + const state = ctx.getState(); + const submission = state.submissions.data.find((s) => s.id === registryId); + + if (submission?.funders && submission.funders.length > 0) { + return; + } + + return this.metadataService.getCustomItemMetadata(registryId).pipe( + tap((res) => { + const newFunders = res.funders; + + ctx.setState( + patch({ + submissions: patch({ + data: updateItem( + (submission) => submission.id === registryId, + patch({ + funders: newFunders, + }) + ), + }), + }) + ); + }), + catchError((error) => { + return handleSectionError(ctx, 'submissions', error); + }) + ); + } } diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.html b/src/app/shared/funder-awards-list/funder-awards-list.component.html index 34cbeb506..5eb8efdf8 100644 --- a/src/app/shared/funder-awards-list/funder-awards-list.component.html +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.html @@ -1,9 +1,8 @@
- @let funders = customItemMetadata()?.funders; - @if (funders?.length) { + @if (funders().length) {

{{ 'resourceCard.labels.funderAwards' | translate }}

- @for (funder of funders; track $index) { + @for (funder of funders(); track $index) { ([]); registryId = input(null); - private store = inject(Store); - customItemMetadata = this.store.selectSignal(MetadataSelectors.getCustomItemMetadata); - - constructor() { - effect(() => { - const registryId = this.registryId(); - if (registryId) { - this.store.dispatch(new GetCustomItemMetadata(registryId)); - } - }); - } } From b1a3710f7e15fd067111b91dd8f975c2bfd5f78b Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 19 Jan 2026 17:13:56 +0200 Subject: [PATCH 6/8] resolve CR comments --- .../registry-pending-submissions.component.html | 3 +-- .../registry-pending-submissions.component.ts | 3 ++- .../registry-submission-item.component.ts | 7 ++----- .../registry-submissions.component.html | 3 +-- .../registry-submissions.component.ts | 3 ++- .../registry-moderation/registry-moderation.state.ts | 8 ++------ 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html index 96a28cac1..7275ce5ce 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.html @@ -39,9 +39,8 @@ [submission]="item" [status]="selectedReviewOption()" (selected)="navigateToRegistration(item)" - (loadContributors)="loadContributors(item)" + (loadAdditionalData)="loadAdditionalData(item)" (loadMoreContributors)="loadMoreContributors(item)" - (loadRegistrySubmissionFunders)="loadRegistrySubmissionFunders(item)" >
} diff --git a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts index d2591cd4e..26b08f29c 100644 --- a/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts +++ b/src/app/features/moderation/components/registry-pending-submissions/registry-pending-submissions.component.ts @@ -131,7 +131,8 @@ export class RegistryPendingSubmissionsComponent implements OnInit { this.actions.loadMoreRegistrySubmissionContributors(item.id); } - loadRegistrySubmissionFunders(item: RegistryModeration) { + loadAdditionalData(item: RegistryModeration) { + this.actions.getRegistrySubmissionContributors(item.id); this.actions.getRegistrySubmissionFunders(item.id); } diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts index f6187acfe..93b331b09 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.ts @@ -39,10 +39,8 @@ import { RegistryModeration } from '../../models'; export class RegistrySubmissionItemComponent { status = input.required(); submission = input.required(); - loadContributors = output(); loadMoreContributors = output(); - loadRegistrySubmissionFunders = output(); - + loadAdditionalData = output(); selected = output(); readonly reviewStatusIcon = ReviewStatusIcon; @@ -70,7 +68,6 @@ export class RegistrySubmissionItemComponent { }); handleOpen() { - this.loadContributors.emit(); - this.loadRegistrySubmissionFunders.emit(); + this.loadAdditionalData.emit(); } } diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html index 38c3afbdd..73386a8f1 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html @@ -39,9 +39,8 @@ [submission]="item" [status]="selectedReviewOption()" (selected)="navigateToRegistration(item)" - (loadContributors)="loadContributors(item)" + (loadAdditionalData)="loadAdditionalData(item)" (loadMoreContributors)="loadMoreContributors(item)" - (loadRegistrySubmissionFunders)="loadRegistrySubmissionFunders(item)" >
} diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts index 468349216..3271d565f 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.ts @@ -131,7 +131,8 @@ export class RegistrySubmissionsComponent implements OnInit { this.actions.loadMoreRegistrySubmissionContributors(item.id); } - loadRegistrySubmissionFunders(item: RegistryModeration) { + loadAdditionalData(item: RegistryModeration) { + this.actions.getRegistrySubmissionContributors(item.id); this.actions.getRegistrySubmissionFunders(item.id); } diff --git a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts index 5145fe66c..9132bab68 100644 --- a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts +++ b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts @@ -168,24 +168,20 @@ export class RegistryModerationState { return this.metadataService.getCustomItemMetadata(registryId).pipe( tap((res) => { - const newFunders = res.funders; - ctx.setState( patch({ submissions: patch({ data: updateItem( (submission) => submission.id === registryId, patch({ - funders: newFunders, + funders: res.funders, }) ), }), }) ); }), - catchError((error) => { - return handleSectionError(ctx, 'submissions', error); - }) + catchError((error) => handleSectionError(ctx, 'submissions', error)) ); } } From d99b935516106530f4876b0751dff0ee23acc703 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 19 Jan 2026 18:20:01 +0200 Subject: [PATCH 7/8] add unittests --- .../funder-awards-list.component.spec.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts b/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts index 4ab0e277c..9614e930c 100644 --- a/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.spec.ts @@ -1,4 +1,10 @@ +import { TranslateModule } from '@ngx-translate/core'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter } from '@angular/router'; + +import { Funder } from '@osf/features/metadata/models'; import { FunderAwardsListComponent } from './funder-awards-list.component'; @@ -6,9 +12,30 @@ describe('FunderAwardsListComponent', () => { let component: FunderAwardsListComponent; let fixture: ComponentFixture; + const MOCK_REGISTRY_ID = 'test-registry-123'; + const MOCK_FUNDERS: Funder[] = [ + { + funderName: 'National Science Foundation', + awardNumber: 'NSF-2024-X', + funderIdentifier: '10.13039/100000001', + funderIdentifierType: 'Crossref Funder ID', + awardUri: 'https://nsf.gov/award/1', + awardTitle: 'Advanced Research Initiative', + }, + { + funderName: 'European Research Council', + awardNumber: 'ERC-888', + funderIdentifier: '10.13039/501100000781', + funderIdentifierType: 'Crossref Funder ID', + awardUri: 'https://erc.europa.eu/award/2', + awardTitle: 'Future Fellowship', + }, + ]; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FunderAwardsListComponent], + imports: [FunderAwardsListComponent, TranslateModule.forRoot()], + providers: [provideRouter([])], }).compileComponents(); fixture = TestBed.createComponent(FunderAwardsListComponent); @@ -19,4 +46,40 @@ describe('FunderAwardsListComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should not render the list or label if funders array is empty', () => { + fixture.componentRef.setInput('funders', []); + fixture.detectChanges(); + const label = fixture.debugElement.query(By.css('p')); + const links = fixture.debugElement.queryAll(By.css('a')); + expect(label).toBeNull(); + expect(links.length).toBe(0); + }); + + it('should render a list of funders when data is provided', () => { + fixture.componentRef.setInput('funders', MOCK_FUNDERS); + fixture.componentRef.setInput('registryId', MOCK_REGISTRY_ID); + fixture.detectChanges(); + const links = fixture.debugElement.queryAll(By.css('a')); + expect(links.length).toBe(2); + const firstItemText = links[0].nativeElement.textContent; + expect(firstItemText).toContain('National Science Foundation'); + expect(firstItemText).toContain('NSF-2024-X'); + }); + + it('should generate the correct router link', () => { + fixture.componentRef.setInput('funders', MOCK_FUNDERS); + fixture.componentRef.setInput('registryId', MOCK_REGISTRY_ID); + fixture.detectChanges(); + const linkDebugEl = fixture.debugElement.query(By.css('a')); + const href = linkDebugEl.nativeElement.getAttribute('href'); + expect(href).toContain(`/${MOCK_REGISTRY_ID}/metadata/osf`); + }); + + it('should open links in a new tab', () => { + fixture.componentRef.setInput('funders', MOCK_FUNDERS); + fixture.detectChanges(); + const linkDebugEl = fixture.debugElement.query(By.css('a')); + expect(linkDebugEl.attributes['target']).toBe('_blank'); + }); }); From 3303385286630c7d46f70f3be9edb5f414fa3557 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 19 Jan 2026 18:21:22 +0200 Subject: [PATCH 8/8] add loading if funders is not yet set --- .../registry-submission-item.component.html | 3 +- .../models/registry-moderation.model.ts | 1 + .../registry-moderation.state.ts | 26 +++++++++++++- .../funder-awards-list.component.html | 34 +++++++++++-------- .../funder-awards-list.component.ts | 5 ++- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html index b73981496..8bc4d4a6e 100644 --- a/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html +++ b/src/app/features/moderation/components/registry-submission-item/registry-submission-item.component.html @@ -64,7 +64,7 @@

{{ submission().title }}

@@ -73,6 +73,7 @@

{{ submission().title }}

diff --git a/src/app/features/moderation/models/registry-moderation.model.ts b/src/app/features/moderation/models/registry-moderation.model.ts index 890bce389..31da4f051 100644 --- a/src/app/features/moderation/models/registry-moderation.model.ts +++ b/src/app/features/moderation/models/registry-moderation.model.ts @@ -20,4 +20,5 @@ export interface RegistryModeration { totalContributors?: number; contributorsPage?: number; funders?: Funder[]; + fundersLoading?: boolean; } diff --git a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts index 9132bab68..5c4715b76 100644 --- a/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts +++ b/src/app/features/moderation/store/registry-moderation/registry-moderation.state.ts @@ -166,6 +166,17 @@ export class RegistryModerationState { return; } + ctx.setState( + patch({ + submissions: patch({ + data: updateItem( + (submission) => submission.id === registryId, + patch({ fundersLoading: true }) + ), + }), + }) + ); + return this.metadataService.getCustomItemMetadata(registryId).pipe( tap((res) => { ctx.setState( @@ -175,13 +186,26 @@ export class RegistryModerationState { (submission) => submission.id === registryId, patch({ funders: res.funders, + fundersLoading: false, }) ), }), }) ); }), - catchError((error) => handleSectionError(ctx, 'submissions', error)) + catchError((error) => { + ctx.setState( + patch({ + submissions: patch({ + data: updateItem( + (submission) => submission.id === registryId, + patch({ fundersLoading: false }) + ), + }), + }) + ); + return handleSectionError(ctx, 'submissions', error); + }) ); } } diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.html b/src/app/shared/funder-awards-list/funder-awards-list.component.html index 5eb8efdf8..95fcdad0b 100644 --- a/src/app/shared/funder-awards-list/funder-awards-list.component.html +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.html @@ -1,18 +1,24 @@
- @if (funders().length) { -

{{ 'resourceCard.labels.funderAwards' | translate }}

-
- @for (funder of funders(); track $index) { - -
{{ funder.funderName }}
- -
{{ funder.awardNumber }}
-
- } + @if (isLoading()) { +
+
+ } @else { + @if (funders().length) { +

{{ 'resourceCard.labels.funderAwards' | translate }}

+
+ @for (funder of funders(); track $index) { + +
{{ funder.funderName }}
+ +
{{ funder.awardNumber }}
+
+ } +
+ } }
diff --git a/src/app/shared/funder-awards-list/funder-awards-list.component.ts b/src/app/shared/funder-awards-list/funder-awards-list.component.ts index 983b8ae72..969bf1053 100644 --- a/src/app/shared/funder-awards-list/funder-awards-list.component.ts +++ b/src/app/shared/funder-awards-list/funder-awards-list.component.ts @@ -1,5 +1,7 @@ import { TranslatePipe } from '@ngx-translate/core'; +import { Skeleton } from 'primeng/skeleton'; + import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { RouterLink } from '@angular/router'; @@ -7,7 +9,7 @@ import { Funder } from '@osf/features/metadata/models'; @Component({ selector: 'osf-funder-awards-list', - imports: [RouterLink, TranslatePipe], + imports: [RouterLink, TranslatePipe, Skeleton], templateUrl: './funder-awards-list.component.html', styleUrl: './funder-awards-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -15,4 +17,5 @@ import { Funder } from '@osf/features/metadata/models'; export class FunderAwardsListComponent { funders = input([]); registryId = input(null); + isLoading = input(false); }