From 8c703d0ddc50b7947bb4acb723d02e089f5f3c34 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Fri, 26 Jun 2026 20:53:04 -0400 Subject: [PATCH 1/2] feat(es2): Load cedar template by ID instead of via the list endpoint --- .../features/metadata/metadata.component.ts | 44 ++++++------------- src/app/shared/services/metadata.service.ts | 7 +++ 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/app/features/metadata/metadata.component.ts b/src/app/features/metadata/metadata.component.ts index f04a46343..4ac270696 100644 --- a/src/app/features/metadata/metadata.component.ts +++ b/src/app/features/metadata/metadata.component.ts @@ -26,6 +26,7 @@ import { MetadataResourceEnum } from '@osf/shared/enums/metadata-resource.enum'; import { ResourceType } from '@osf/shared/enums/resource-type.enum'; import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service'; import { CustomDialogService } from '@osf/shared/services/custom-dialog.service'; +import { MetadataService } from '@osf/shared/services/metadata.service'; import { SignpostingService } from '@osf/shared/services/signposting.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { CollectionsSelectors, GetProjectSubmissions } from '@osf/shared/stores/collections'; @@ -129,6 +130,7 @@ export class MetadataComponent implements OnInit, OnDestroy { private readonly customConfirmationService = inject(CustomConfirmationService); private readonly environment = inject(ENVIRONMENT); private readonly signpostingService = inject(SignpostingService); + private readonly metadataService = inject(MetadataService); private readonly activeFlags = select(UserSelectors.getActiveFlags); readonly collectionSubmissionWithCedar = computed(() => @@ -234,23 +236,6 @@ export class MetadataComponent implements OnInit, OnDestroy { this.handleRouteBasedTabSelection(); }); - effect(() => { - const templates = this.cedarTemplates(); - const selectedRecord = this.selectedCedarRecord(); - - if (selectedRecord && templates?.data && !this.selectedCedarTemplate()) { - const templateId = selectedRecord.relationships?.template?.data?.id; - if (templateId) { - const template = templates.data.find((t) => t.id === templateId); - if (template) { - this.selectedCedarTemplate.set(template); - } else if (templates.links?.next) { - this.actions.getCedarTemplates(templates.links.next); - } - } - } - }); - effect(() => { const metadata = this.metadata(); @@ -567,13 +552,11 @@ export class MetadataComponent implements OnInit, OnDestroy { private loadCedarRecord(recordId: string): void { const records = this.cedarRecords(); - const templates = this.cedarTemplates(); if (!records) { return; } const record = records.find((r) => r.id === recordId); - if (!record) { return; } @@ -582,21 +565,20 @@ export class MetadataComponent implements OnInit, OnDestroy { this.cedarFormReadonly.set(true); const templateId = record.relationships?.template?.data?.id; - - if (templateId && templates?.data) { - const template = templates.data.find((t) => t.id === templateId); - if (template) { - this.selectedCedarTemplate.set(template); - } else { - this.selectedCedarTemplate.set(null); - this.actions.getCedarTemplates(); - } - } else { - this.selectedCedarTemplate.set(null); - this.actions.getCedarTemplates(); + if (templateId) { + this.loadCedarTemplateById(templateId); } } + private loadCedarTemplateById(templateId: string): void { + this.metadataService + .getMetadataCedarTemplate(templateId) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((response) => { + this.selectedCedarTemplate.set(response.data); + }); + } + private handleRouteBasedTabSelection(): void { const recordId = this.activeRoute.snapshot.paramMap.get('recordId'); diff --git a/src/app/shared/services/metadata.service.ts b/src/app/shared/services/metadata.service.ts index 97eb40f2b..3a58a94a4 100644 --- a/src/app/shared/services/metadata.service.ts +++ b/src/app/shared/services/metadata.service.ts @@ -6,6 +6,7 @@ import { inject, Injectable } from '@angular/core'; import { ENVIRONMENT } from '@core/provider/environment.provider'; import { CedarRecordsMapper, MetadataMapper, RorMapper } from '@osf/features/metadata/mappers'; import { + CedarMetadataDataTemplateJsonApi, CedarMetadataRecord, CedarMetadataRecordJsonApi, CedarMetadataTemplateJsonApi, @@ -103,6 +104,12 @@ export class MetadataService { ); } + getMetadataCedarTemplate(id: string): Observable<{ data: CedarMetadataDataTemplateJsonApi }> { + return this.jsonApiService.get<{ data: CedarMetadataDataTemplateJsonApi }>( + `${this.apiDomainUrl}/_/cedar_metadata_templates/${id}/` + ); + } + getMetadataCedarRecords( resourceId: string, resourceType: ResourceType, From 49771e7957911daa2d6d14b17b42cc17640cf083 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Fri, 26 Jun 2026 21:24:09 -0400 Subject: [PATCH 2/2] feat(es2): Fix same issue on overview page --- .../project-overview-metadata.component.ts | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.ts b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.ts index 1bda04633..4bdacbd9b 100644 --- a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.ts +++ b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.ts @@ -5,16 +5,13 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, DestroyRef, effect, inject, signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Router, RouterLink } from '@angular/router'; import { UserSelectors } from '@core/store/user'; -import { - GetCedarMetadataRecords, - GetCedarMetadataTemplates, - GetCustomItemMetadata, - MetadataSelectors, -} from '@osf/features/metadata/store'; +import { CedarMetadataDataTemplateJsonApi } from '@osf/features/metadata/models'; +import { GetCedarMetadataRecords, GetCustomItemMetadata, MetadataSelectors } from '@osf/features/metadata/store'; import { AffiliatedInstitutionsViewComponent } from '@osf/shared/components/affiliated-institutions-view/affiliated-institutions-view.component'; import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component'; import { FundersListComponent } from '@osf/shared/components/funders-list/funders-list.component'; @@ -27,6 +24,7 @@ import { TruncatedTextComponent } from '@osf/shared/components/truncated-text/tr import { CurrentResourceType, ResourceType } from '@osf/shared/enums/resource-type.enum'; import { LanguageLabelPipe } from '@osf/shared/pipes/language-label.pipe'; import { ResourceTypeGeneralLabelPipe } from '@osf/shared/pipes/resource-type-general-label.pipe'; +import { MetadataService } from '@osf/shared/services/metadata.service'; import { CollectionsSelectors, GetProjectSubmissions } from '@osf/shared/stores/collections'; import { ContributorsSelectors, @@ -74,6 +72,8 @@ import { OverviewSupplementsComponent } from '../overview-supplements/overview-s }) export class ProjectOverviewMetadataComponent { private readonly router = inject(Router); + private readonly destroyRef = inject(DestroyRef); + private readonly metadataService = inject(MetadataService); readonly currentProject = select(ProjectOverviewSelectors.getProject); readonly isAnonymous = select(ProjectOverviewSelectors.isProjectAnonymous); @@ -97,10 +97,11 @@ export class ProjectOverviewMetadataComponent { readonly isProjectSubmissionsLoading = select(CollectionsSelectors.getCurrentProjectSubmissionsLoading); readonly activeFlags = select(UserSelectors.getActiveFlags); readonly cedarRecords = select(MetadataSelectors.getCedarRecords); - private readonly cedarTemplatesResponse = select(MetadataSelectors.getCedarTemplates); - readonly cedarTemplates = computed(() => this.cedarTemplatesResponse()?.data ?? null); readonly isCedarMode = computed(() => this.activeFlags().includes(COLLECTION_SUBMISSION_WITH_CEDAR)); + private readonly cedarTemplatesMap = signal>(new Map()); + readonly cedarTemplates = computed(() => [...this.cedarTemplatesMap().values()]); + readonly resourceType = CurrentResourceType.Projects; readonly dateFormat = 'MMM d, y, h:mm a'; @@ -116,7 +117,6 @@ export class ProjectOverviewMetadataComponent { getBibliographicContributors: GetBibliographicContributors, loadMoreBibliographicContributors: LoadMoreBibliographicContributors, getCedarRecords: GetCedarMetadataRecords, - getCedarTemplates: GetCedarMetadataTemplates, }); constructor() { @@ -124,6 +124,7 @@ export class ProjectOverviewMetadataComponent { const project = this.currentProject(); if (project?.id) { + this.cedarTemplatesMap.set(new Map()); this.actions.getBibliographicContributors(project.id, ResourceType.Project); this.actions.getInstitutions(project.id); this.actions.getIdentifiers(project.id); @@ -132,10 +133,28 @@ export class ProjectOverviewMetadataComponent { this.actions.getProjectSubmissions(project.id); this.actions.getLicense(project.licenseId); this.actions.getCedarRecords(project.id, ResourceType.Project); - this.actions.getCedarTemplates(); this.actions.getCustomItemMetadata(project.id); } }); + + effect(() => { + const records = this.cedarRecords(); + if (!records?.length) return; + + const currentMap = this.cedarTemplatesMap(); + const missingIds = [ + ...new Set(records.map((r) => r.relationships?.template?.data?.id).filter((id): id is string => !!id)), + ].filter((id) => !currentMap.has(id)); + + missingIds.forEach((id) => { + this.metadataService + .getMetadataCedarTemplate(id) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((response) => { + this.cedarTemplatesMap.update((map) => new Map(map).set(id, response.data)); + }); + }); + }); } onCustomCitationUpdated(citation: string): void {