- @for (attribute of attributes; track attribute.key) {
-
- {{ attribute.label }}: {{ attribute.value }}
-
- }
+ @if (isCedarMode() && cedarRecord && cedarTemplate?.attributes?.template) {
+
+
+ } @else {
+ @let attributes = getSubmissionAttributes(submission);
+
+ @if (attributes.length) {
+
+ @for (attribute of attributes; track attribute.key) {
+
+ {{ attribute.label }}: {{ attribute.value }}
+
+ }
+
+ }
}
diff --git a/src/app/features/project/overview/components/overview-collections/overview-collections.component.spec.ts b/src/app/features/project/overview/components/overview-collections/overview-collections.component.spec.ts
index 020aee7b6..c279a7aef 100644
--- a/src/app/features/project/overview/components/overview-collections/overview-collections.component.spec.ts
+++ b/src/app/features/project/overview/components/overview-collections/overview-collections.component.spec.ts
@@ -2,8 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { collectionFilterNames } from '@osf/features/collections/constants';
+import { CedarMetadataDataTemplateJsonApi, CedarMetadataRecordData } from '@osf/features/metadata/models';
import { CollectionSubmission } from '@osf/shared/models/collections/collections.model';
+import { CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK } from '@testing/mocks/cedar-metadata-data-template-json-api.mock';
+import { MOCK_CEDAR_METADATA_RECORD_DATA } from '@testing/mocks/cedar-metadata-record.mock';
import {
MOCK_COLLECTION_SUBMISSION_EMPTY_FILTERS,
MOCK_COLLECTION_SUBMISSION_SINGLE_FILTER,
@@ -92,4 +95,74 @@ describe('OverviewCollectionsComponent', () => {
expect(statusAttr?.value).toBe('1');
expect(typeof statusAttr?.value).toBe('string');
});
+
+ it('should render cedar-artifact-viewer when isCedarMode is true with matching record and template', async () => {
+ const cedarSubmission: CollectionSubmission = {
+ ...MOCK_COLLECTION_SUBMISSION_EMPTY_FILTERS,
+ requiredMetadataTemplateId: 'template-1',
+ };
+ const cedarRecord: CedarMetadataRecordData = MOCK_CEDAR_METADATA_RECORD_DATA;
+ const cedarTemplate: CedarMetadataDataTemplateJsonApi =
+ CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK as CedarMetadataDataTemplateJsonApi;
+
+ fixture.componentRef.setInput('projectSubmissions', [cedarSubmission]);
+ fixture.componentRef.setInput('isCedarMode', true);
+ fixture.componentRef.setInput('cedarRecords', [cedarRecord]);
+ fixture.componentRef.setInput('cedarTemplates', [cedarTemplate]);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const viewer = fixture.nativeElement.querySelector('cedar-artifact-viewer');
+ expect(viewer).toBeTruthy();
+ });
+
+ it('should not render cedar-artifact-viewer when isCedarMode is false', async () => {
+ const cedarSubmission: CollectionSubmission = {
+ ...MOCK_COLLECTION_SUBMISSION_WITH_FILTERS,
+ requiredMetadataTemplateId: 'template-1',
+ };
+ const cedarRecord: CedarMetadataRecordData = MOCK_CEDAR_METADATA_RECORD_DATA;
+ const cedarTemplate: CedarMetadataDataTemplateJsonApi =
+ CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK as CedarMetadataDataTemplateJsonApi;
+
+ fixture.componentRef.setInput('projectSubmissions', [cedarSubmission]);
+ fixture.componentRef.setInput('isCedarMode', false);
+ fixture.componentRef.setInput('cedarRecords', [cedarRecord]);
+ fixture.componentRef.setInput('cedarTemplates', [cedarTemplate]);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const viewer = fixture.nativeElement.querySelector('cedar-artifact-viewer');
+ expect(viewer).toBeNull();
+ });
+
+ it('should show traditional attributes when isCedarMode is true but no matching record', async () => {
+ const cedarSubmission: CollectionSubmission = {
+ ...MOCK_COLLECTION_SUBMISSION_WITH_FILTERS,
+ requiredMetadataTemplateId: 'non-existent-template',
+ };
+
+ fixture.componentRef.setInput('projectSubmissions', [cedarSubmission]);
+ fixture.componentRef.setInput('isCedarMode', true);
+ fixture.componentRef.setInput('cedarRecords', []);
+ fixture.componentRef.setInput('cedarTemplates', []);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const viewer = fixture.nativeElement.querySelector('cedar-artifact-viewer');
+ expect(viewer).toBeNull();
+ expect(component.getSubmissionAttributes(cedarSubmission).length).toBeGreaterThan(0);
+ });
+
+ it('should compute empty cedarRecordByTemplateId map when cedarRecords is null', () => {
+ fixture.componentRef.setInput('cedarRecords', null);
+ fixture.detectChanges();
+ expect(component.cedarRecordByTemplateId().size).toBe(0);
+ });
+
+ it('should compute empty cedarTemplateById map when cedarTemplates is null', () => {
+ fixture.componentRef.setInput('cedarTemplates', null);
+ fixture.detectChanges();
+ expect(component.cedarTemplateById().size).toBe(0);
+ });
});
diff --git a/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts b/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts
index 168a3530b..a9c51aa97 100644
--- a/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts
+++ b/src/app/features/project/overview/components/overview-collections/overview-collections.component.ts
@@ -5,10 +5,19 @@ import { Button } from 'primeng/button';
import { Skeleton } from 'primeng/skeleton';
import { Tag } from 'primeng/tag';
-import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ CUSTOM_ELEMENTS_SCHEMA,
+ input,
+ ViewEncapsulation,
+} from '@angular/core';
import { RouterLink } from '@angular/router';
import { collectionFilterNames } from '@osf/features/collections/constants';
+import { CEDAR_VIEWER_CONFIG } from '@osf/features/metadata/constants';
+import { CedarMetadataDataTemplateJsonApi, CedarMetadataRecordData } from '@osf/features/metadata/models';
import { StopPropagationDirective } from '@osf/shared/directives/stop-propagation.directive';
import { CollectionSubmission } from '@osf/shared/models/collections/collections.model';
import { KeyValueModel } from '@osf/shared/models/common/key-value.model';
@@ -32,10 +41,32 @@ import { CollectionStatusSeverityPipe } from '@osf/shared/pipes/collection-statu
templateUrl: './overview-collections.component.html',
styleUrl: './overview-collections.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ encapsulation: ViewEncapsulation.None,
})
export class OverviewCollectionsComponent {
projectSubmissions = input
(null);
isProjectSubmissionsLoading = input(false);
+ isCedarMode = input(false);
+ cedarRecords = input(null);
+ cedarTemplates = input(null);
+
+ cedarViewerConfig = CEDAR_VIEWER_CONFIG;
+
+ cedarRecordByTemplateId = computed(() => {
+ const records = this.cedarRecords();
+ return new Map(
+ records?.flatMap((record) => {
+ const templateId = record.relationships?.template?.data?.id;
+ return templateId ? [[templateId, record] as const] : [];
+ }) ?? []
+ );
+ });
+
+ cedarTemplateById = computed(() => {
+ const templates = this.cedarTemplates();
+ return new Map(templates?.map((t) => [t.id, t] as const) ?? []);
+ });
getSubmissionAttributes(submission: CollectionSubmission): KeyValueModel[] {
const attributes: KeyValueModel[] = [];
diff --git a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.html b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.html
index 8c9b34ff6..e918fdafe 100644
--- a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.html
+++ b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.html
@@ -83,6 +83,9 @@ {{ 'common.labels.affiliatedInstitutions' | translate }}
diff --git a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.spec.ts b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.spec.ts
index f896c2108..13202745e 100644
--- a/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.spec.ts
+++ b/src/app/features/project/overview/components/project-overview-metadata/project-overview-metadata.component.spec.ts
@@ -7,6 +7,8 @@ import { Mock } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
+import { UserSelectors } from '@core/store/user';
+import { GetCedarMetadataRecords, GetCedarMetadataTemplates, 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 { ResourceCitationsComponent } from '@osf/shared/components/resource-citations/resource-citations.component';
@@ -94,6 +96,9 @@ describe('ProjectOverviewMetadataComponent', () => {
{ selector: ContributorsSelectors.hasMoreBibliographicContributors, value: false },
{ selector: CollectionsSelectors.getCurrentProjectSubmissions, value: [] },
{ selector: CollectionsSelectors.getCurrentProjectSubmissionsLoading, value: false },
+ { selector: UserSelectors.getActiveFlags, value: [] },
+ { selector: MetadataSelectors.getCedarRecords, value: [] },
+ { selector: MetadataSelectors.getCedarTemplates, value: null },
],
}),
],
@@ -122,6 +127,8 @@ describe('ProjectOverviewMetadataComponent', () => {
expect(dispatchMock).toHaveBeenCalledWith(new FetchSelectedSubjects('project-1', ResourceType.Project));
expect(dispatchMock).toHaveBeenCalledWith(new GetProjectSubmissions('project-1'));
expect(dispatchMock).toHaveBeenCalledWith(new GetProjectLicense(MOCK_PROJECT_OVERVIEW.licenseId));
+ expect(dispatchMock).toHaveBeenCalledWith(new GetCedarMetadataRecords('project-1', ResourceType.Project));
+ expect(dispatchMock).toHaveBeenCalledWith(new GetCedarMetadataTemplates());
});
it('should not dispatch init actions when project is null', () => {
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 40e0507ae..55b3e571a 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,9 +5,11 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { DatePipe } from '@angular/common';
-import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
+import { UserSelectors } from '@core/store/user';
+import { GetCedarMetadataRecords, GetCedarMetadataTemplates, 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 { ResourceCitationsComponent } from '@osf/shared/components/resource-citations/resource-citations.component';
@@ -24,6 +26,7 @@ import {
LoadMoreBibliographicContributors,
} from '@osf/shared/stores/contributors';
import { FetchSelectedSubjects, SubjectsSelectors } from '@osf/shared/stores/subjects';
+import { COLLECTION_SUBMISSION_WITH_CEDAR } from '@shared/constants/feature-flags.const';
import {
GetProjectIdentifiers,
@@ -79,6 +82,11 @@ export class ProjectOverviewMetadataComponent {
readonly hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors);
readonly projectSubmissions = select(CollectionsSelectors.getCurrentProjectSubmissions);
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));
readonly resourceType = CurrentResourceType.Projects;
readonly dateFormat = 'MMM d, y, h:mm a';
@@ -93,6 +101,8 @@ export class ProjectOverviewMetadataComponent {
getProjectSubmissions: GetProjectSubmissions,
getBibliographicContributors: GetBibliographicContributors,
loadMoreBibliographicContributors: LoadMoreBibliographicContributors,
+ getCedarRecords: GetCedarMetadataRecords,
+ getCedarTemplates: GetCedarMetadataTemplates,
});
constructor() {
@@ -107,6 +117,8 @@ export class ProjectOverviewMetadataComponent {
this.actions.getSubjects(project.id, ResourceType.Project);
this.actions.getProjectSubmissions(project.id);
this.actions.getLicense(project.licenseId);
+ this.actions.getCedarRecords(project.id, ResourceType.Project);
+ this.actions.getCedarTemplates();
}
});
}