From 11e5c0b97a3fdca62bc8ee11aff4687c85deeb44 Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Fri, 29 Aug 2025 15:46:35 +0300 Subject: [PATCH 1/6] feat(datacite-tracker): implemented datacite view tracking for registries and preprints --- .../mappers/preprint-providers.mapper.ts | 2 +- .../preprints/mappers/preprints.mapper.ts | 2 ++ .../models/preprint-json-api.models.ts | 2 ++ .../preprints/models/preprint.models.ts | 2 ++ .../preprint-details.component.ts | 15 +++++++++--- .../preprints/services/preprints.service.ts | 2 +- .../mappers/project-overview.mapper.ts | 8 ++----- .../models/project-overview.models.ts | 23 ++++--------------- .../mappers/registry-overview.mapper.ts | 8 ++----- .../get-registry-overview-json-api.model.ts | 12 ++-------- .../features/registry/registry.component.ts | 16 ++++++++++++- ...metadata-publication-doi.component.spec.ts | 4 ++-- ...ject-metadata-publication-doi.component.ts | 4 ++-- src/app/shared/mappers/identifiers.mapper.ts | 15 ++++++++++++ .../models/identifiers/identifier-json-api.ts | 12 ++++++++++ .../models/identifiers/indentifier.model.ts | 6 +++++ src/app/shared/models/identifiers/index.ts | 2 ++ src/app/shared/models/index.ts | 1 + src/environments/environment.local.ts | 22 ++++++++++++++++++ 19 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 src/app/shared/mappers/identifiers.mapper.ts create mode 100644 src/app/shared/models/identifiers/identifier-json-api.ts create mode 100644 src/app/shared/models/identifiers/indentifier.model.ts create mode 100644 src/app/shared/models/identifiers/index.ts diff --git a/src/app/features/preprints/mappers/preprint-providers.mapper.ts b/src/app/features/preprints/mappers/preprint-providers.mapper.ts index 5fc610f1e..8237ec52c 100644 --- a/src/app/features/preprints/mappers/preprint-providers.mapper.ts +++ b/src/app/features/preprints/mappers/preprint-providers.mapper.ts @@ -31,7 +31,7 @@ export class PreprintProvidersMapper { backgroundColor: brandRaw.attributes.background_color, }, iri: response.links.iri, - faviconUrl: response.attributes.assets.favicon, + faviconUrl: response.attributes.assets?.favicon, squareColorNoTransparentImageUrl: response.attributes.assets?.square_color_no_transparent, reviewsWorkflow: response.attributes.reviews_workflow, facebookAppId: response.attributes.facebook_app_id, diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 317d881ea..91cfa453f 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -1,6 +1,7 @@ import { LicensesMapper } from '@osf/shared/mappers'; import { ApiData, JsonApiResponseWithMeta, ResponseJsonApi } from '@osf/shared/models'; import { StringOrNull } from '@shared/helpers'; +import { IdentifiersMapper } from '@shared/mappers/identifiers.mapper'; import { Preprint, @@ -136,6 +137,7 @@ export class PreprintsMapper { views: meta.metrics.views, }, embeddedLicense: LicensesMapper.fromLicenseDataJsonApi(data.embeds.license.data), + identifiers: IdentifiersMapper.fromEmbeds(data.embeds.identifiers), preprintDoiLink: links.preprint_doi, articleDoiLink: links.doi, }; diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index 858555abf..56e606ac3 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -1,6 +1,7 @@ import { UserPermissions } from '@osf/shared/enums'; import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; import { ContributorResponse, LicenseRecordJsonApi, LicenseResponseJsonApi } from '@osf/shared/models'; +import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -69,6 +70,7 @@ export interface PreprintEmbedsJsonApi { data: ContributorResponse[]; }; license: LicenseResponseJsonApi; + identifiers: IdentifiersEmbedJsonApiResponse; } export interface PreprintMetaJsonApi { diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index 11a7283bd..aaa88c3d0 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -1,6 +1,7 @@ import { UserPermissions } from '@osf/shared/enums'; import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; import { IdName, License, LicenseOptions } from '@osf/shared/models'; +import { Identifier } from '@shared/models/identifiers/indentifier.model'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -43,6 +44,7 @@ export interface Preprint { embeddedLicense?: License; preprintDoiLink?: string; articleDoiLink?: string; + identifiers?: Identifier[]; } export interface PreprintFilesLinks { diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index adc514452..cdcd0dc30 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -6,7 +6,7 @@ import { Button } from 'primeng/button'; import { DialogService } from 'primeng/dynamicdialog'; import { Skeleton } from 'primeng/skeleton'; -import { filter, map, of } from 'rxjs'; +import { filter, map, Observable, of } from 'rxjs'; import { DatePipe, Location } from '@angular/common'; import { @@ -19,7 +19,7 @@ import { OnDestroy, OnInit, } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; import { UserSelectors } from '@core/store/user'; @@ -46,6 +46,7 @@ import { import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; import { IS_MEDIUM, pathJoin } from '@osf/shared/helpers'; +import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { ReviewPermissions, UserPermissions } from '@shared/enums'; import { MetaTagsService } from '@shared/services'; import { ContributorsSelectors } from '@shared/stores'; @@ -75,7 +76,7 @@ import { environment } from 'src/environments/environment'; providers: [DialogService, DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PreprintDetailsComponent implements OnInit, OnDestroy { +export class PreprintDetailsComponent extends DataciteTrackerComponent implements OnInit, OnDestroy { @HostBinding('class') classes = 'flex-1 flex flex-column w-full'; private readonly router = inject(Router); @@ -105,6 +106,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); preprint = select(PreprintSelectors.getPreprint); + preprint$ = toObservable(select(PreprintSelectors.getPreprint)); isPreprintLoading = select(PreprintSelectors.isPreprintLoading); contributors = select(ContributorsSelectors.getContributors); areContributorsLoading = select(ContributorsSelectors.isContributorsLoading); @@ -281,12 +283,19 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy { this.fetchPreprint(this.preprintId()); }, }); + this.setupDataciteViewTrackerEffect().subscribe(); } ngOnDestroy() { this.actions.resetState(); } + protected getDoi(): Observable { + return this.preprint$.pipe( + filter((project) => project != null), + map((project) => project?.identifiers?.find((item) => item.category == 'doi')?.value ?? null) + ); + } fetchPreprintVersion(preprintVersionId: string) { const currentUrl = this.router.url; const newUrl = currentUrl.replace(/[^/]+$/, preprintVersionId); diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index d52fde721..5d33e63ef 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -77,7 +77,7 @@ export class PreprintsService { const params = { 'metrics[views]': 'total', 'metrics[downloads]': 'total', - 'embed[]': 'license', + 'embed[]': ['license', 'identifiers'], }; return this.jsonApiService .get< diff --git a/src/app/features/project/overview/mappers/project-overview.mapper.ts b/src/app/features/project/overview/mappers/project-overview.mapper.ts index 7488be1d0..e51985440 100644 --- a/src/app/features/project/overview/mappers/project-overview.mapper.ts +++ b/src/app/features/project/overview/mappers/project-overview.mapper.ts @@ -1,4 +1,5 @@ import { InstitutionsMapper } from '@shared/mappers'; +import { IdentifiersMapper } from '@shared/mappers/identifiers.mapper'; import { License } from '@shared/models'; import { ProjectOverview, ProjectOverviewGetResponseJsoApi } from '../models'; @@ -46,12 +47,7 @@ export class ProjectOverviewMapper { type: contributor.embeds.users.data.type, })), affiliatedInstitutions: InstitutionsMapper.fromInstitutionsResponse(response.embeds.affiliated_institutions), - identifiers: response.embeds.identifiers?.data.map((identifier) => ({ - id: identifier.id, - type: identifier.type, - value: identifier.attributes.value, - category: identifier.attributes.category, - })), + identifiers: IdentifiersMapper.fromEmbeds(response.embeds.identifiers), ...(response.embeds.storage?.data && !response.embeds.storage?.errors && { storage: { diff --git a/src/app/features/project/overview/models/project-overview.models.ts b/src/app/features/project/overview/models/project-overview.models.ts index cf67e88ec..a35dafc8d 100644 --- a/src/app/features/project/overview/models/project-overview.models.ts +++ b/src/app/features/project/overview/models/project-overview.models.ts @@ -1,5 +1,6 @@ import { UserPermissions } from '@osf/shared/enums'; -import { Institution, InstitutionsJsonApiResponse, JsonApiResponse, License } from '@osf/shared/models'; +import { Identifier, Institution, InstitutionsJsonApiResponse, JsonApiResponse, License } from '@osf/shared/models'; +import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; export interface ProjectOverviewContributor { familyName: string; @@ -38,7 +39,7 @@ export interface ProjectOverview { storageLimitStatus: string; storageUsage: string; }; - identifiers?: ProjectIdentifiers[]; + identifiers?: Identifier[]; supplements?: ProjectSupplements[]; analyticsKey: string; currentUserCanComment: boolean; @@ -100,16 +101,7 @@ export interface ProjectOverviewGetResponseJsoApi { }; embeds: { affiliated_institutions: InstitutionsJsonApiResponse; - identifiers: { - data: { - id: string; - type: string; - attributes: { - category: string; - value: string; - }; - }[]; - }; + identifiers: IdentifiersEmbedJsonApiResponse; bibliographic_contributors: { data: { embeds: { @@ -208,13 +200,6 @@ export interface ProjectOverviewResponseJsonApi extends JsonApiResponse ({ - id: identifier.id, - type: identifier.type, - value: identifier.attributes.value, - category: identifier.attributes.category, - })), + identifiers: IdentifiersMapper.fromEmbeds(data.embeds.identifiers), analyticsKey: data.attributes?.analyticsKey, currentUserCanComment: data.attributes.current_user_can_comment, currentUserPermissions: data.attributes.current_user_permissions, diff --git a/src/app/features/registry/models/get-registry-overview-json-api.model.ts b/src/app/features/registry/models/get-registry-overview-json-api.model.ts index 379a8b76f..7e5f4abad 100644 --- a/src/app/features/registry/models/get-registry-overview-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-overview-json-api.model.ts @@ -1,5 +1,6 @@ import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; import { ApiData, JsonApiResponse, ProviderDataJsonApi, SchemaResponseDataJsonApi } from '@osf/shared/models'; +import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; export type GetRegistryOverviewJsonApi = JsonApiResponse; @@ -87,16 +88,7 @@ export interface RegistryOverviewJsonApiEmbed { }; }; }; - identifiers: { - data: { - id: string; - type: string; - attributes: { - category: string; - value: string; - }; - }[]; - }; + identifiers: IdentifiersEmbedJsonApiResponse; schema_responses: { data: SchemaResponseDataJsonApi[]; }; diff --git a/src/app/features/registry/registry.component.ts b/src/app/features/registry/registry.component.ts index ac8910fbf..44d3b753a 100644 --- a/src/app/features/registry/registry.component.ts +++ b/src/app/features/registry/registry.component.ts @@ -1,11 +1,15 @@ import { select } from '@ngxs/store'; +import { filter, map, Observable } from 'rxjs'; + import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, effect, HostBinding, inject } from '@angular/core'; +import { toObservable } from '@angular/core/rxjs-interop'; import { RouterOutlet } from '@angular/router'; import { pathJoin } from '@osf/shared/helpers'; import { MetaTagsService } from '@osf/shared/services'; +import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { RegistryOverviewSelectors } from './store/registry-overview'; @@ -19,20 +23,30 @@ import { environment } from 'src/environments/environment'; changeDetection: ChangeDetectionStrategy.OnPush, providers: [DatePipe], }) -export class RegistryComponent { +export class RegistryComponent extends DataciteTrackerComponent { @HostBinding('class') classes = 'flex-1 flex flex-column'; private readonly metaTags = inject(MetaTagsService); private readonly datePipe = inject(DatePipe); protected readonly registry = select(RegistryOverviewSelectors.getRegistry); + readonly registry$ = toObservable(select(RegistryOverviewSelectors.getRegistry)); constructor() { + super(); effect(() => { if (this.registry()) { this.setMetaTags(); } }); + this.setupDataciteViewTrackerEffect().subscribe(); + } + + protected getDoi(): Observable { + return this.registry$.pipe( + filter((project) => project != null), + map((project) => project?.identifiers?.find((item) => item.category == 'doi')?.value ?? null) + ); } private setMetaTags(): void { diff --git a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts index 002cd128a..6195ae260 100644 --- a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts +++ b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { MOCK_PROJECT_IDENTIFIERS, TranslateServiceMock } from '@shared/mocks'; +import { Identifier } from '@shared/models'; import { ProjectMetadataPublicationDoiComponent } from './project-metadata-publication-doi.component'; @@ -9,7 +9,7 @@ describe('ProjectMetadataPublicationDoiComponent', () => { let component: ProjectMetadataPublicationDoiComponent; let fixture: ComponentFixture; - const mockIdentifiers: ProjectIdentifiers = MOCK_PROJECT_IDENTIFIERS; + const mockIdentifiers: Identifier = MOCK_PROJECT_IDENTIFIERS; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts index fc06cc2fe..6c83ac343 100644 --- a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts +++ b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts @@ -5,7 +5,7 @@ import { Card } from 'primeng/card'; import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; +import { Identifier } from '@osf/shared/models'; @Component({ selector: 'osf-project-metadata-publication-doi', @@ -16,6 +16,6 @@ import { ProjectIdentifiers } from '@osf/features/project/overview/models'; export class ProjectMetadataPublicationDoiComponent { openEditPublicationDoiDialog = output(); - identifiers = input([]); + identifiers = input([]); hideEditDoi = input(false); } diff --git a/src/app/shared/mappers/identifiers.mapper.ts b/src/app/shared/mappers/identifiers.mapper.ts new file mode 100644 index 000000000..0802a9ab7 --- /dev/null +++ b/src/app/shared/mappers/identifiers.mapper.ts @@ -0,0 +1,15 @@ +import { Identifier, ResponseJsonApi } from '@shared/models'; +import { IdentifiersEmbedJsonApiData } from '@shared/models/identifiers/identifier-json-api'; + +export class IdentifiersMapper { + static fromEmbeds(response: ResponseJsonApi): Identifier[] { + return response.data.map((rawIdentifier) => { + return { + category: rawIdentifier.attributes.category, + value: rawIdentifier.attributes.value, + id: rawIdentifier.id, + type: rawIdentifier.type, + }; + }); + } +} diff --git a/src/app/shared/models/identifiers/identifier-json-api.ts b/src/app/shared/models/identifiers/identifier-json-api.ts new file mode 100644 index 000000000..078a245c0 --- /dev/null +++ b/src/app/shared/models/identifiers/identifier-json-api.ts @@ -0,0 +1,12 @@ +import { ApiData, ResponseJsonApi } from '@shared/models'; + +export type IdentifiersEmbedJsonApiResponse = ResponseJsonApi; +export type IdentifiersEmbedJsonApiData = ApiData; + +export interface IdentifierAttributes { + category: string; + value: string; +} +interface IdentifierLinks { + self: string; +} diff --git a/src/app/shared/models/identifiers/indentifier.model.ts b/src/app/shared/models/identifiers/indentifier.model.ts new file mode 100644 index 000000000..c15b35688 --- /dev/null +++ b/src/app/shared/models/identifiers/indentifier.model.ts @@ -0,0 +1,6 @@ +export interface Identifier { + id: string; + type: string; + category: string; + value: string; +} diff --git a/src/app/shared/models/identifiers/index.ts b/src/app/shared/models/identifiers/index.ts new file mode 100644 index 000000000..00cdac15c --- /dev/null +++ b/src/app/shared/models/identifiers/index.ts @@ -0,0 +1,2 @@ +// export * from './identifier-json-api'; +export * from './indentifier.model'; diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index dfa9a7433..51eee33ef 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -17,6 +17,7 @@ export * from './filter-labels.model'; export * from './filters'; export * from './google-drive-folder.model'; export * from './guid-response-json-api.model'; +export * from './identifiers'; export * from './institutions'; export * from './language-code.model'; export * from './license'; diff --git a/src/environments/environment.local.ts b/src/environments/environment.local.ts index 19c5638ce..31c69529e 100644 --- a/src/environments/environment.local.ts +++ b/src/environments/environment.local.ts @@ -18,4 +18,26 @@ export const environment = { defaultProvider: 'osf', dataciteTrackerRepoId: null, dataciteTrackerAddress: 'https://analytics.datacite.org/api/metric', + google: { + /** + * OAuth 2.0 Client ID used to identify the application during Google authentication. + * Registered in Google Cloud Console under "OAuth 2.0 Client IDs". + * Safe to expose in frontend code. + * @see https://console.cloud.google.com/apis/credentials + */ + GOOGLE_FILE_PICKER_CLIENT_ID: '610901277352-m5krehjdtu8skh2teq85fb7mvk411qa6.apps.googleusercontent.com', + /** + * Public API key used to load Google Picker and other Google APIs that don’t require user auth. + * Must be restricted by referrer in Google Cloud Console. + * Exposing this key is acceptable if restricted properly. + * @see https://developers.google.com/maps/api-key-best-practices + */ + GOOGLE_FILE_PICKER_API_KEY: 'AIzaSyA3EnD0pOv4v7sJt7BGuR1i2Gcj-Gju6C0', + /** + * Google Cloud Project App ID. + * Used for associating API requests with the specific Google project. + * Required for Google Picker configuration. + */ + GOOGLE_FILE_PICKER_APP_ID: 610901277352, + }, }; From 9ae4195df634416c3ae0b240416d20996cbf3cce Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Fri, 29 Aug 2025 18:07:08 +0300 Subject: [PATCH 2/6] chore(datacite-tracker): refactored doi extraction to be less repetitive --- .../preprint-details.component.ts | 9 ++++----- .../overview/project-overview.component.ts | 10 ++++------ src/app/features/registry/registry.component.ts | 10 ++++------ .../datacite-tracker.component.ts | 15 +++++++++------ 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index cdcd0dc30..e13b42ab5 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -48,6 +48,7 @@ import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/prepri import { IS_MEDIUM, pathJoin } from '@osf/shared/helpers'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { ReviewPermissions, UserPermissions } from '@shared/enums'; +import { Identifier } from '@shared/models'; import { MetaTagsService } from '@shared/services'; import { ContributorsSelectors } from '@shared/stores'; @@ -290,12 +291,10 @@ export class PreprintDetailsComponent extends DataciteTrackerComponent implement this.actions.resetState(); } - protected getDoi(): Observable { - return this.preprint$.pipe( - filter((project) => project != null), - map((project) => project?.identifiers?.find((item) => item.category == 'doi')?.value ?? null) - ); + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { + return this.preprint$; } + fetchPreprintVersion(preprintVersionId: string) { const currentUrl = this.router.url; const newUrl = currentUrl.replace(/[^/]+$/, preprintVersionId); diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index 894ff6ab0..740d7b06d 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -7,7 +7,7 @@ import { DialogService } from 'primeng/dynamicdialog'; import { Message } from 'primeng/message'; import { TagModule } from 'primeng/tag'; -import { filter, map, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { CommonModule } from '@angular/common'; import { @@ -35,6 +35,7 @@ import { import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { Mode, ResourceType, UserPermissions } from '@shared/enums'; import { MapProjectOverview } from '@shared/mappers/resource-overview.mappers'; +import { Identifier } from '@shared/models'; import { ToastService } from '@shared/services'; import { ClearWiki, @@ -191,11 +192,8 @@ export class ProjectOverviewComponent extends DataciteTrackerComponent implement return null; }); - protected getDoi(): Observable { - return this.currentProject$.pipe( - filter((project) => project != null), - map((project) => project?.identifiers?.find((item) => item.category == 'doi')?.value ?? null) - ); + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { + return this.currentProject$; } constructor() { diff --git a/src/app/features/registry/registry.component.ts b/src/app/features/registry/registry.component.ts index 44d3b753a..f29413406 100644 --- a/src/app/features/registry/registry.component.ts +++ b/src/app/features/registry/registry.component.ts @@ -1,6 +1,6 @@ import { select } from '@ngxs/store'; -import { filter, map, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, effect, HostBinding, inject } from '@angular/core'; @@ -10,6 +10,7 @@ import { RouterOutlet } from '@angular/router'; import { pathJoin } from '@osf/shared/helpers'; import { MetaTagsService } from '@osf/shared/services'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; +import { Identifier } from '@shared/models'; import { RegistryOverviewSelectors } from './store/registry-overview'; @@ -42,11 +43,8 @@ export class RegistryComponent extends DataciteTrackerComponent { this.setupDataciteViewTrackerEffect().subscribe(); } - protected getDoi(): Observable { - return this.registry$.pipe( - filter((project) => project != null), - map((project) => project?.identifiers?.find((item) => item.category == 'doi')?.value ?? null) - ); + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { + return this.registry$; } private setMetaTags(): void { diff --git a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts index 25a58e204..627302f8b 100644 --- a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts +++ b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts @@ -1,20 +1,21 @@ -import { filter, Observable, switchMap, take } from 'rxjs'; +import { filter, map, Observable, switchMap, take } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { Identifier } from '@shared/models'; import { DataciteService } from '@shared/services/datacite/datacite.service'; @Injectable() export abstract class DataciteTrackerComponent { private dataciteService = inject(DataciteService); - /** - * Abstract method to retrieve the DOI (Digital Object Identifier) of the resource. + * Abstract method to retrieve an observable of resource to be tracked. + * This method is generic enough to support all objects that have `identifiers` property. * Must be implemented by subclasses. * - * @returns An Observable that emits a string DOI or null if unavailable. + * @returns An Observable that emits an item which may contain DOI identifier or null . */ - protected abstract getDoi(): Observable; + protected abstract get trackable(): Observable<{ identifiers?: Identifier[] } | null>; /** * Sets up a one-time effect to log a "view" event to Datacite for the resource DOI. @@ -24,7 +25,9 @@ export abstract class DataciteTrackerComponent { * @returns An Observable that completes after logging the view. */ protected setupDataciteViewTrackerEffect(): Observable { - return this.getDoi().pipe( + return this.trackable.pipe( + filter((item) => item != null), + map((item) => item?.identifiers?.find((identifier) => identifier.category == 'doi')?.value ?? null), take(1), filter((doi): doi is string => !!doi), switchMap((doi) => this.dataciteService.logView(doi)) From 224879b67e7ceeee56823ac4d295ba853da49ec1 Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Mon, 1 Sep 2025 17:49:07 +0300 Subject: [PATCH 3/6] fix(datacite-tracker): reverted undesired refactor --- .../preprints/mappers/preprints.mapper.ts | 8 +++++-- .../models/preprint-json-api.models.ts | 12 ++++++++-- .../preprints/models/preprint.models.ts | 4 ++-- .../preprint-details.component.ts | 4 ++-- .../mappers/project-overview.mapper.ts | 8 +++++-- .../models/project-overview.models.ts | 23 +++++++++++++++---- .../overview/project-overview.component.ts | 4 ++-- .../mappers/registry-overview.mapper.ts | 8 +++++-- .../get-registry-overview-json-api.model.ts | 12 ++++++++-- .../features/registry/registry.component.ts | 4 ++-- .../datacite-tracker.component.ts | 4 ++-- ...metadata-publication-doi.component.spec.ts | 4 ++-- ...ject-metadata-publication-doi.component.ts | 4 ++-- src/app/shared/mappers/identifiers.mapper.ts | 15 ------------ .../models/identifiers/identifier-json-api.ts | 12 ---------- .../models/identifiers/indentifier.model.ts | 6 ----- src/app/shared/models/identifiers/index.ts | 2 -- src/app/shared/models/index.ts | 1 - 18 files changed, 71 insertions(+), 64 deletions(-) delete mode 100644 src/app/shared/mappers/identifiers.mapper.ts delete mode 100644 src/app/shared/models/identifiers/identifier-json-api.ts delete mode 100644 src/app/shared/models/identifiers/indentifier.model.ts delete mode 100644 src/app/shared/models/identifiers/index.ts diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 91cfa453f..4c8a6e630 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -1,7 +1,6 @@ import { LicensesMapper } from '@osf/shared/mappers'; import { ApiData, JsonApiResponseWithMeta, ResponseJsonApi } from '@osf/shared/models'; import { StringOrNull } from '@shared/helpers'; -import { IdentifiersMapper } from '@shared/mappers/identifiers.mapper'; import { Preprint, @@ -137,7 +136,12 @@ export class PreprintsMapper { views: meta.metrics.views, }, embeddedLicense: LicensesMapper.fromLicenseDataJsonApi(data.embeds.license.data), - identifiers: IdentifiersMapper.fromEmbeds(data.embeds.identifiers), + identifiers: data.embeds.identifiers?.data.map((identifier) => ({ + id: identifier.id, + type: identifier.type, + value: identifier.attributes.value, + category: identifier.attributes.category, + })), preprintDoiLink: links.preprint_doi, articleDoiLink: links.doi, }; diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index 56e606ac3..fb33f8820 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -1,7 +1,6 @@ import { UserPermissions } from '@osf/shared/enums'; import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; import { ContributorResponse, LicenseRecordJsonApi, LicenseResponseJsonApi } from '@osf/shared/models'; -import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -70,7 +69,16 @@ export interface PreprintEmbedsJsonApi { data: ContributorResponse[]; }; license: LicenseResponseJsonApi; - identifiers: IdentifiersEmbedJsonApiResponse; + identifiers: { + data: { + id: string; + type: string; + attributes: { + category: string; + value: string; + }; + }[]; + }; } export interface PreprintMetaJsonApi { diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index aaa88c3d0..eb786334d 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -1,7 +1,7 @@ +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { UserPermissions } from '@osf/shared/enums'; import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; import { IdName, License, LicenseOptions } from '@osf/shared/models'; -import { Identifier } from '@shared/models/identifiers/indentifier.model'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -44,7 +44,7 @@ export interface Preprint { embeddedLicense?: License; preprintDoiLink?: string; articleDoiLink?: string; - identifiers?: Identifier[]; + identifiers?: ProjectIdentifiers[]; } export interface PreprintFilesLinks { diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index e13b42ab5..fca23133d 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -45,10 +45,10 @@ import { } from '@osf/features/preprints/store/preprint'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { IS_MEDIUM, pathJoin } from '@osf/shared/helpers'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { ReviewPermissions, UserPermissions } from '@shared/enums'; -import { Identifier } from '@shared/models'; import { MetaTagsService } from '@shared/services'; import { ContributorsSelectors } from '@shared/stores'; @@ -291,7 +291,7 @@ export class PreprintDetailsComponent extends DataciteTrackerComponent implement this.actions.resetState(); } - protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { + protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { return this.preprint$; } diff --git a/src/app/features/project/overview/mappers/project-overview.mapper.ts b/src/app/features/project/overview/mappers/project-overview.mapper.ts index e51985440..7488be1d0 100644 --- a/src/app/features/project/overview/mappers/project-overview.mapper.ts +++ b/src/app/features/project/overview/mappers/project-overview.mapper.ts @@ -1,5 +1,4 @@ import { InstitutionsMapper } from '@shared/mappers'; -import { IdentifiersMapper } from '@shared/mappers/identifiers.mapper'; import { License } from '@shared/models'; import { ProjectOverview, ProjectOverviewGetResponseJsoApi } from '../models'; @@ -47,7 +46,12 @@ export class ProjectOverviewMapper { type: contributor.embeds.users.data.type, })), affiliatedInstitutions: InstitutionsMapper.fromInstitutionsResponse(response.embeds.affiliated_institutions), - identifiers: IdentifiersMapper.fromEmbeds(response.embeds.identifiers), + identifiers: response.embeds.identifiers?.data.map((identifier) => ({ + id: identifier.id, + type: identifier.type, + value: identifier.attributes.value, + category: identifier.attributes.category, + })), ...(response.embeds.storage?.data && !response.embeds.storage?.errors && { storage: { diff --git a/src/app/features/project/overview/models/project-overview.models.ts b/src/app/features/project/overview/models/project-overview.models.ts index a35dafc8d..cf67e88ec 100644 --- a/src/app/features/project/overview/models/project-overview.models.ts +++ b/src/app/features/project/overview/models/project-overview.models.ts @@ -1,6 +1,5 @@ import { UserPermissions } from '@osf/shared/enums'; -import { Identifier, Institution, InstitutionsJsonApiResponse, JsonApiResponse, License } from '@osf/shared/models'; -import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; +import { Institution, InstitutionsJsonApiResponse, JsonApiResponse, License } from '@osf/shared/models'; export interface ProjectOverviewContributor { familyName: string; @@ -39,7 +38,7 @@ export interface ProjectOverview { storageLimitStatus: string; storageUsage: string; }; - identifiers?: Identifier[]; + identifiers?: ProjectIdentifiers[]; supplements?: ProjectSupplements[]; analyticsKey: string; currentUserCanComment: boolean; @@ -101,7 +100,16 @@ export interface ProjectOverviewGetResponseJsoApi { }; embeds: { affiliated_institutions: InstitutionsJsonApiResponse; - identifiers: IdentifiersEmbedJsonApiResponse; + identifiers: { + data: { + id: string; + type: string; + attributes: { + category: string; + value: string; + }; + }[]; + }; bibliographic_contributors: { data: { embeds: { @@ -200,6 +208,13 @@ export interface ProjectOverviewResponseJsonApi extends JsonApiResponse { + protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { return this.currentProject$; } diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index 4d8a04e2b..636a62cc1 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -1,7 +1,6 @@ import { RegistryOverview, RegistryOverviewJsonApiData } from '@osf/features/registry/models'; import { ReviewPermissionsMapper } from '@osf/shared/mappers'; import { RegistrationMapper } from '@osf/shared/mappers/registration'; -import { IdentifiersMapper } from '@shared/mappers/identifiers.mapper'; import { MapRegistryStatus } from '@shared/mappers/registry/map-registry-status.mapper'; export function MapRegistryOverview(data: RegistryOverviewJsonApiData): RegistryOverview | null { @@ -37,7 +36,12 @@ export function MapRegistryOverview(data: RegistryOverviewJsonApiData): Registry middleName: contributor?.embeds?.users?.data?.attributes?.middle_names, type: contributor?.embeds?.users?.data?.type, })), - identifiers: IdentifiersMapper.fromEmbeds(data.embeds.identifiers), + identifiers: data.embeds.identifiers?.data.map((identifier) => ({ + id: identifier.id, + type: identifier.type, + value: identifier.attributes.value, + category: identifier.attributes.category, + })), analyticsKey: data.attributes?.analyticsKey, currentUserCanComment: data.attributes.current_user_can_comment, currentUserPermissions: data.attributes.current_user_permissions, diff --git a/src/app/features/registry/models/get-registry-overview-json-api.model.ts b/src/app/features/registry/models/get-registry-overview-json-api.model.ts index 7e5f4abad..379a8b76f 100644 --- a/src/app/features/registry/models/get-registry-overview-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-overview-json-api.model.ts @@ -1,6 +1,5 @@ import { RegistrationReviewStates, RevisionReviewStates } from '@osf/shared/enums'; import { ApiData, JsonApiResponse, ProviderDataJsonApi, SchemaResponseDataJsonApi } from '@osf/shared/models'; -import { IdentifiersEmbedJsonApiResponse } from '@shared/models/identifiers/identifier-json-api'; export type GetRegistryOverviewJsonApi = JsonApiResponse; @@ -88,7 +87,16 @@ export interface RegistryOverviewJsonApiEmbed { }; }; }; - identifiers: IdentifiersEmbedJsonApiResponse; + identifiers: { + data: { + id: string; + type: string; + attributes: { + category: string; + value: string; + }; + }[]; + }; schema_responses: { data: SchemaResponseDataJsonApi[]; }; diff --git a/src/app/features/registry/registry.component.ts b/src/app/features/registry/registry.component.ts index f29413406..b8fd0931a 100644 --- a/src/app/features/registry/registry.component.ts +++ b/src/app/features/registry/registry.component.ts @@ -7,10 +7,10 @@ import { ChangeDetectionStrategy, Component, effect, HostBinding, inject } from import { toObservable } from '@angular/core/rxjs-interop'; import { RouterOutlet } from '@angular/router'; +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { pathJoin } from '@osf/shared/helpers'; import { MetaTagsService } from '@osf/shared/services'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; -import { Identifier } from '@shared/models'; import { RegistryOverviewSelectors } from './store/registry-overview'; @@ -43,7 +43,7 @@ export class RegistryComponent extends DataciteTrackerComponent { this.setupDataciteViewTrackerEffect().subscribe(); } - protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { + protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { return this.registry$; } diff --git a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts index 627302f8b..2a4a8eb44 100644 --- a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts +++ b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts @@ -2,7 +2,7 @@ import { filter, map, Observable, switchMap, take } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { Identifier } from '@shared/models'; +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { DataciteService } from '@shared/services/datacite/datacite.service'; @Injectable() @@ -15,7 +15,7 @@ export abstract class DataciteTrackerComponent { * * @returns An Observable that emits an item which may contain DOI identifier or null . */ - protected abstract get trackable(): Observable<{ identifiers?: Identifier[] } | null>; + protected abstract get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null>; /** * Sets up a one-time effect to log a "view" event to Datacite for the resource DOI. diff --git a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts index 6195ae260..002cd128a 100644 --- a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts +++ b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { MOCK_PROJECT_IDENTIFIERS, TranslateServiceMock } from '@shared/mocks'; -import { Identifier } from '@shared/models'; import { ProjectMetadataPublicationDoiComponent } from './project-metadata-publication-doi.component'; @@ -9,7 +9,7 @@ describe('ProjectMetadataPublicationDoiComponent', () => { let component: ProjectMetadataPublicationDoiComponent; let fixture: ComponentFixture; - const mockIdentifiers: Identifier = MOCK_PROJECT_IDENTIFIERS; + const mockIdentifiers: ProjectIdentifiers = MOCK_PROJECT_IDENTIFIERS; beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts index 6c83ac343..fc06cc2fe 100644 --- a/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts +++ b/src/app/shared/components/shared-metadata/components/project-metadata-publication-doi/project-metadata-publication-doi.component.ts @@ -5,7 +5,7 @@ import { Card } from 'primeng/card'; import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; -import { Identifier } from '@osf/shared/models'; +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; @Component({ selector: 'osf-project-metadata-publication-doi', @@ -16,6 +16,6 @@ import { Identifier } from '@osf/shared/models'; export class ProjectMetadataPublicationDoiComponent { openEditPublicationDoiDialog = output(); - identifiers = input([]); + identifiers = input([]); hideEditDoi = input(false); } diff --git a/src/app/shared/mappers/identifiers.mapper.ts b/src/app/shared/mappers/identifiers.mapper.ts deleted file mode 100644 index 0802a9ab7..000000000 --- a/src/app/shared/mappers/identifiers.mapper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Identifier, ResponseJsonApi } from '@shared/models'; -import { IdentifiersEmbedJsonApiData } from '@shared/models/identifiers/identifier-json-api'; - -export class IdentifiersMapper { - static fromEmbeds(response: ResponseJsonApi): Identifier[] { - return response.data.map((rawIdentifier) => { - return { - category: rawIdentifier.attributes.category, - value: rawIdentifier.attributes.value, - id: rawIdentifier.id, - type: rawIdentifier.type, - }; - }); - } -} diff --git a/src/app/shared/models/identifiers/identifier-json-api.ts b/src/app/shared/models/identifiers/identifier-json-api.ts deleted file mode 100644 index 078a245c0..000000000 --- a/src/app/shared/models/identifiers/identifier-json-api.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiData, ResponseJsonApi } from '@shared/models'; - -export type IdentifiersEmbedJsonApiResponse = ResponseJsonApi; -export type IdentifiersEmbedJsonApiData = ApiData; - -export interface IdentifierAttributes { - category: string; - value: string; -} -interface IdentifierLinks { - self: string; -} diff --git a/src/app/shared/models/identifiers/indentifier.model.ts b/src/app/shared/models/identifiers/indentifier.model.ts deleted file mode 100644 index c15b35688..000000000 --- a/src/app/shared/models/identifiers/indentifier.model.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Identifier { - id: string; - type: string; - category: string; - value: string; -} diff --git a/src/app/shared/models/identifiers/index.ts b/src/app/shared/models/identifiers/index.ts deleted file mode 100644 index 00cdac15c..000000000 --- a/src/app/shared/models/identifiers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// export * from './identifier-json-api'; -export * from './indentifier.model'; diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 51eee33ef..dfa9a7433 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -17,7 +17,6 @@ export * from './filter-labels.model'; export * from './filters'; export * from './google-drive-folder.model'; export * from './guid-response-json-api.model'; -export * from './identifiers'; export * from './institutions'; export * from './language-code.model'; export * from './license'; From 98c64c7642ebe2ad899b19dbb9be52921d8aab7e Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Wed, 3 Sep 2025 15:04:38 +0300 Subject: [PATCH 4/6] chore(datacite-tracker): added tests to registry component --- jest.config.js | 1 - .../registry/registry.component.spec.ts | 88 +++++++++++++++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/jest.config.js b/jest.config.js index 0f184fe80..6d66ac915 100644 --- a/jest.config.js +++ b/jest.config.js @@ -70,7 +70,6 @@ module.exports = { testPathIgnorePatterns: [ '/src/app/app.config.ts', '/src/app/app.routes.ts', - '/src/app/features/registry/', '/src/app/features/project/addons/components/configure-configure-addon/', '/src/app/features/project/addons/components/connect-configured-addon/', '/src/app/features/project/addons/components/disconnect-addon-modal/', diff --git a/src/app/features/registry/registry.component.spec.ts b/src/app/features/registry/registry.component.spec.ts index f3bf7dc7d..7671e819c 100644 --- a/src/app/features/registry/registry.component.spec.ts +++ b/src/app/features/registry/registry.component.spec.ts @@ -1,22 +1,94 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Store } from '@ngxs/store'; + +import { of } from 'rxjs'; + +import { DatePipe } from '@angular/common'; +import { signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { ProjectIdentifiers } from '@osf/features/project/overview/models'; +import { RegistryOverviewSelectors } from '@osf/features/registry/store/registry-overview'; +import { MetaTagsService } from '@osf/shared/services'; +import { DataciteService } from '@shared/services/datacite/datacite.service'; import { RegistryComponent } from './registry.component'; describe('RegistryComponent', () => { - let component: RegistryComponent; - let fixture: ComponentFixture; + let fixture: any; + let dataciteService: jest.Mocked; + + const registrySignal = signal(null); beforeEach(async () => { + dataciteService = { + logView: jest.fn().mockReturnValue(of(void 0)), + } as unknown as jest.Mocked; + + const mockStore = { + selectSignal: jest.fn((selector: any) => { + if (selector === RegistryOverviewSelectors.getRegistry) { + return registrySignal; // return a signal, not an observable + } + return signal(null); + }), + }; + await TestBed.configureTestingModule({ - imports: [RegistryComponent], + imports: [RegistryComponent], // standalone component + providers: [ + { provide: Store, useValue: mockStore }, + DatePipe, + { provide: DataciteService, useValue: dataciteService }, + { + provide: MetaTagsService, + useValue: { updateMetaTagsForRoute: jest.fn() }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(RegistryComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + TestBed.inject(MetaTagsService); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('reacts to sequence of state changes', () => { + registrySignal.set(null); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + registrySignal.set(getRegistry([])); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + registrySignal.set(getRegistry([{ category: 'dio', value: '123', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + registrySignal.set(getRegistry([{ category: 'doi', value: '123', id: '', type: 'identifier' }])); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalled(); + + registrySignal.set(getRegistry([{ category: 'doi', value: '456', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenLastCalledWith('123'); }); }); + +function getRegistry(identifiers: ProjectIdentifiers[]) { + return { + id: 'r1', + title: 'Mock Registry', + description: 'Test description', + dateRegistered: new Date('2023-01-01'), + dateModified: new Date('2023-02-01'), + doi: '10.1000/mockdoi', + tags: ['angular', 'jest'], + license: { name: 'MIT' }, + contributors: [ + { givenName: 'Alice', familyName: 'Smith' }, + { givenName: 'Bob', familyName: 'Brown' }, + ], + identifiers: identifiers, + }; +} From f50ac5c8db6f12f8e7f4f77f9043d0c4c7673b27 Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Wed, 3 Sep 2025 15:09:36 +0300 Subject: [PATCH 5/6] fix(datacite-tracker): fixed datacite tracker effect --- .../components/datacite-tracker/datacite-tracker.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts index 2a4a8eb44..ff4733fdf 100644 --- a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts +++ b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts @@ -28,8 +28,8 @@ export abstract class DataciteTrackerComponent { return this.trackable.pipe( filter((item) => item != null), map((item) => item?.identifiers?.find((identifier) => identifier.category == 'doi')?.value ?? null), - take(1), filter((doi): doi is string => !!doi), + take(1), switchMap((doi) => this.dataciteService.logView(doi)) ); } From f57b8c0af09615f89dc28e0aa96a8e30f32b82ad Mon Sep 17 00:00:00 2001 From: Oleh Paduchak Date: Thu, 4 Sep 2025 15:51:47 +0300 Subject: [PATCH 6/6] chore(datacite-tracker): added tests to project and preprint components --- jest.config.js | 10 +- .../preprints/models/preprint.models.ts | 9 +- .../preprint-details.component.spec.ts | 51 ++++- .../preprint-details.component.ts | 4 +- .../project-overview.component.spec.ts | 176 +++++++++++++++++- .../overview/project-overview.component.ts | 5 +- .../registry/registry.component.spec.ts | 4 +- .../features/registry/registry.component.ts | 5 +- .../datacite-tracker.component.ts | 4 +- .../resource-metadata.component.ts | 3 +- 10 files changed, 237 insertions(+), 34 deletions(-) diff --git a/jest.config.js b/jest.config.js index 283ff7937..561b52d19 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,7 +8,7 @@ module.exports = { '^@osf/(.*)$': '/src/app/$1', '^@core/(.*)$': '/src/app/core/$1', '^@shared/(.*)$': '/src/app/shared/$1', - '^@styles/(.*)$': '/src/styles/$1', + '^@styles/(.*)$': '/assets/styles/$1', '^@testing/(.*)$': '/src/testing/$1', '^src/environments/environment$': '/src/environments/environment.ts', }, @@ -76,9 +76,10 @@ module.exports = { '/src/app/features/project/addons/components/confirm-account-connection-modal/', '/src/app/features/files/', '/src/app/features/my-projects/', - '/src/app/features/preprints/', + '/src/app/features/project/analytics/', '/src/app/features/project/contributors/', - '/src/app/features/project/overview/', + '/src/app/features/project/files/', + '/src/app/features/project/metadata/', '/src/app/features/project/registrations', '/src/app/features/project/settings', '/src/app/features/project/wiki', @@ -97,6 +98,9 @@ module.exports = { '/src/app/shared/components/pie-chart/', '/src/app/shared/components/resource-citations/', '/src/app/shared/components/reusable-filter/', + '/src/app/shared/components/shared-metadata/dialogs/affiliated-institutions-dialog/', + '/src/app/shared/components/shared-metadata/dialogs/contributors-dialog/', + '/src/app/shared/components/shared-metadata/shared-metadata', '/src/app/shared/components/subjects/', '/src/app/shared/components/wiki/edit-section/', '/src/app/shared/components/wiki/wiki-list/', diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index eb786334d..3033b133d 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -1,7 +1,6 @@ -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; -import { UserPermissions } from '@osf/shared/enums'; -import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers'; -import { IdName, License, LicenseOptions } from '@osf/shared/models'; +import { Identifier, IdName, License, LicenseOptions } from '@osf/shared/models'; +import { UserPermissions } from '@shared/enums'; +import { BooleanOrNull, StringOrNull } from '@shared/helpers'; import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums'; @@ -44,7 +43,7 @@ export interface Preprint { embeddedLicense?: License; preprintDoiLink?: string; articleDoiLink?: string; - identifiers?: ProjectIdentifiers[]; + identifiers?: Identifier[]; } export interface PreprintFilesLinks { diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts index bf49a36ec..515161bb0 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts @@ -5,7 +5,9 @@ import { MockComponents, MockPipe, MockProvider } from 'ng-mocks'; import { of } from 'rxjs'; +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; import { ActivatedRoute, Router } from '@angular/router'; import { AdditionalInfoComponent } from '@osf/features/preprints/components/preprint-details/additional-info/additional-info.component'; @@ -15,15 +17,21 @@ import { ShareAndDownloadComponent } from '@osf/features/preprints/components/pr import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { MOCK_PROVIDER, MOCK_STORE, TranslateServiceMock } from '@shared/mocks'; +import { Identifier } from '@shared/models'; +import { DataciteService } from '@shared/services/datacite/datacite.service'; import { PreprintDetailsComponent } from './preprint-details.component'; -describe.skip('PreprintDetailsComponent', () => { +describe('PreprintDetailsComponent', () => { let component: PreprintDetailsComponent; let fixture: ComponentFixture; + let dataciteService: jest.Mocked; + + const preprintSignal = signal({ id: 'p1', title: 'Test', description: '' }); const mockRoute: Partial = { params: of({ providerId: 'osf', preprintId: 'p1' }), + queryParams: of({ providerId: 'osf', preprintId: 'p1' }), }; beforeEach(async () => { @@ -34,13 +42,17 @@ describe.skip('PreprintDetailsComponent', () => { case PreprintProvidersSelectors.isPreprintProviderDetailsLoading: return () => false; case PreprintSelectors.getPreprint: - return () => ({ id: 'p1', title: 'Test', description: '' }); + return preprintSignal; case PreprintSelectors.isPreprintLoading: return () => false; default: return () => []; } }); + (MOCK_STORE.dispatch as jest.Mock).mockImplementation(() => of()); + dataciteService = { + logView: jest.fn().mockReturnValue(of(void 0)), + } as unknown as jest.Mocked; await TestBed.configureTestingModule({ imports: [ @@ -55,6 +67,8 @@ describe.skip('PreprintDetailsComponent', () => { ], providers: [ MockProvider(Store, MOCK_STORE), + provideNoopAnimations(), + { provide: DataciteService, useValue: dataciteService }, MockProvider(Router), MockProvider(ActivatedRoute, mockRoute), TranslateServiceMock, @@ -66,11 +80,36 @@ describe.skip('PreprintDetailsComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); - it('isOsfPreprint should be true if providerId === osf', () => { expect(component.isOsfPreprint()).toBeTruthy(); }); + + it('reacts to sequence of state changes', () => { + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + preprintSignal.set(getPreprint([])); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + preprintSignal.set(getPreprint([{ category: 'dio', value: '123', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + preprintSignal.set(getPreprint([{ category: 'doi', value: '123', id: '', type: 'identifier' }])); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalled(); + + preprintSignal.set(getPreprint([{ category: 'doi', value: '456', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenLastCalledWith('123'); + }); }); + +function getPreprint(identifiers: Identifier[]) { + return { + identifiers: identifiers, + }; +} diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index fca23133d..e13b42ab5 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -45,10 +45,10 @@ import { } from '@osf/features/preprints/store/preprint'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { IS_MEDIUM, pathJoin } from '@osf/shared/helpers'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; import { ReviewPermissions, UserPermissions } from '@shared/enums'; +import { Identifier } from '@shared/models'; import { MetaTagsService } from '@shared/services'; import { ContributorsSelectors } from '@shared/stores'; @@ -291,7 +291,7 @@ export class PreprintDetailsComponent extends DataciteTrackerComponent implement this.actions.resetState(); } - protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { return this.preprint$; } diff --git a/src/app/features/project/overview/project-overview.component.spec.ts b/src/app/features/project/overview/project-overview.component.spec.ts index 96a420dba..9a37bbde7 100644 --- a/src/app/features/project/overview/project-overview.component.spec.ts +++ b/src/app/features/project/overview/project-overview.component.spec.ts @@ -1,26 +1,188 @@ -import { MockComponent } from 'ng-mocks'; +import { Store } from '@ngxs/store'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockProvider } from 'ng-mocks'; + +import { ButtonGroupModule } from 'primeng/buttongroup'; +import { DialogService } from 'primeng/dynamicdialog'; +import { Message } from 'primeng/message'; +import { TagModule } from 'primeng/tag'; + +import { of } from 'rxjs'; + +import { DestroyRef, signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { SubHeaderComponent } from '@osf/shared/components'; +import { CollectionSubmissionReviewAction } from '@osf/features/moderation/models'; +import { CollectionsModerationSelectors } from '@osf/features/moderation/store/collections-moderation'; +import { ProjectOverviewSelectors } from '@osf/features/project/overview/store'; +import { + LoadingSpinnerComponent, + ResourceMetadataComponent, + SubHeaderComponent, + ViewOnlyLinkMessageComponent, +} from '@osf/shared/components'; +import { ToastService } from '@osf/shared/services'; +import { MOCK_STORE } from '@shared/mocks'; +import { Identifier } from '@shared/models'; +import { DataciteService } from '@shared/services/datacite/datacite.service'; +import { + BookmarksSelectors, + CitationsSelectors, + CollectionsSelectors, + MyResourcesSelectors, + NodeLinksSelectors, +} from '@shared/stores'; +import { ActivityLogsSelectors } from '@shared/stores/activity-logs'; +import { + LinkedResourcesComponent, + OverviewComponentsComponent, + OverviewToolbarComponent, + OverviewWikiComponent, + RecentActivityComponent, +} from './components'; import { ProjectOverviewComponent } from './project-overview.component'; +import { OSFTestingModule } from '@testing/osf.testing.module'; + +const sampleReviewAction: CollectionSubmissionReviewAction = { + id: 'ra1', + type: 'collection-submission-review-action', + dateCreated: '2025-09-01T10:15:00Z', + dateModified: '2025-09-01T10:30:00Z', + fromState: 'pending', + toState: 'accepted', + comment: 'Submission approved by moderator', + trigger: 'accept', + targetId: 'sub123', + targetNodeId: 'node456', + createdBy: 'user789', +}; + describe('ProjectOverviewComponent', () => { - let component: ProjectOverviewComponent; let fixture: ComponentFixture; + let dataciteService: jest.Mocked; + const projectSignal = signal(getProject()); + + const activatedRouteMock = { + snapshot: { + queryParams: { + mode: 'moderation', // or whatever value you want to test + }, + params: { + id: '1234', // value for this.route.snapshot.params['id'] + }, + parent: { + snapshot: { + params: { + id: '5678', // fallback if top-level param is undefined + }, + }, + }, + }, + }; beforeEach(async () => { + (MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => { + switch (selector) { + case ProjectOverviewSelectors.getProject: + return projectSignal; + case CollectionsModerationSelectors.getCurrentReviewAction: + return signal(sampleReviewAction); + case ProjectOverviewSelectors.getProjectLoading: + case CollectionsSelectors.getCollectionProviderLoading: + case CollectionsModerationSelectors.getCurrentReviewActionLoading: + case ProjectOverviewSelectors.isProjectAnonymous: + case MyResourcesSelectors.getBookmarksLoading: + case BookmarksSelectors.getBookmarksCollectionIdSubmitting: + case ProjectOverviewSelectors.getComponentsLoading: + case NodeLinksSelectors.getLinkedResourcesLoading: + case ActivityLogsSelectors.getActivityLogsLoading: + return () => false; + case ActivityLogsSelectors.getActivityLogsTotalCount: + return () => 0; + case BookmarksSelectors.getBookmarksCollectionId: + return () => '123'; + case MyResourcesSelectors.getBookmarks: + case ActivityLogsSelectors.getActivityLogs: + case CitationsSelectors.getCitationStyles: + case ProjectOverviewSelectors.getComponents: + case NodeLinksSelectors.getLinkedResources: + return () => []; + default: + return () => ''; + } + }); + + dataciteService = { + logView: jest.fn().mockReturnValue(of(void 0)), + } as unknown as jest.Mocked; + await TestBed.configureTestingModule({ - imports: [ProjectOverviewComponent, MockComponent(SubHeaderComponent)], + imports: [ + ProjectOverviewComponent, + OSFTestingModule, + ButtonGroupModule, + TagModule, + SubHeaderComponent, + FormsModule, + LoadingSpinnerComponent, + OverviewWikiComponent, + OverviewComponentsComponent, + LinkedResourcesComponent, + RecentActivityComponent, + OverviewToolbarComponent, + ResourceMetadataComponent, + TranslatePipe, + Message, + RouterLink, + ViewOnlyLinkMessageComponent, + ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRouteMock }, + { provide: Store, useValue: MOCK_STORE }, + { provide: DataciteService, useValue: dataciteService }, + Router, + DestroyRef, + MockProvider(ToastService), + DialogService, + TranslateService, + ], }).compileComponents(); fixture = TestBed.createComponent(ProjectOverviewComponent); - component = fixture.componentInstance; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('reacts to sequence of state changes', () => { + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + projectSignal.set(getProject()); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + projectSignal.set(getProject([{ category: 'dio', value: '123', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalledTimes(0); + + projectSignal.set(getProject([{ category: 'doi', value: '123', id: '', type: 'identifier' }])); + + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenCalled(); + + projectSignal.set(getProject([{ category: 'doi', value: '456', id: '', type: 'identifier' }])); + fixture.detectChanges(); + expect(dataciteService.logView).toHaveBeenLastCalledWith('123'); }); + + function getProject(identifiers?: Identifier[]) { + return { + identifiers: identifiers ?? [], + }; + } }); diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index bcdc4762b..546b0d3fc 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -25,8 +25,6 @@ import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; - import { ClearCollectionModeration, CollectionsModerationSelectors, @@ -54,6 +52,7 @@ import { SubHeaderComponent, ViewOnlyLinkMessageComponent, } from '@shared/components'; +import { Identifier } from '@shared/models'; import { LinkedResourcesComponent, @@ -203,7 +202,7 @@ export class ProjectOverviewComponent extends DataciteTrackerComponent implement return null; }); - protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { return this.currentProject$; } diff --git a/src/app/features/registry/registry.component.spec.ts b/src/app/features/registry/registry.component.spec.ts index 7671e819c..4e46094d3 100644 --- a/src/app/features/registry/registry.component.spec.ts +++ b/src/app/features/registry/registry.component.spec.ts @@ -6,9 +6,9 @@ import { DatePipe } from '@angular/common'; import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { RegistryOverviewSelectors } from '@osf/features/registry/store/registry-overview'; import { MetaTagsService } from '@osf/shared/services'; +import { Identifier } from '@shared/models'; import { DataciteService } from '@shared/services/datacite/datacite.service'; import { RegistryComponent } from './registry.component'; @@ -75,7 +75,7 @@ describe('RegistryComponent', () => { }); }); -function getRegistry(identifiers: ProjectIdentifiers[]) { +function getRegistry(identifiers: Identifier[]) { return { id: 'r1', title: 'Mock Registry', diff --git a/src/app/features/registry/registry.component.ts b/src/app/features/registry/registry.component.ts index 813e36a07..2191f6496 100644 --- a/src/app/features/registry/registry.component.ts +++ b/src/app/features/registry/registry.component.ts @@ -7,10 +7,10 @@ import { ChangeDetectionStrategy, Component, effect, HostBinding, inject } from import { toObservable } from '@angular/core/rxjs-interop'; import { RouterOutlet } from '@angular/router'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; import { pathJoin } from '@osf/shared/helpers'; import { MetaTagsService } from '@osf/shared/services'; import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component'; +import { Identifier } from '@shared/models'; import { RegistryOverviewSelectors } from './store/registry-overview'; @@ -33,7 +33,6 @@ export class RegistryComponent extends DataciteTrackerComponent { readonly registry = select(RegistryOverviewSelectors.getRegistry); readonly registry$ = toObservable(select(RegistryOverviewSelectors.getRegistry)); - constructor() { super(); effect(() => { @@ -44,7 +43,7 @@ export class RegistryComponent extends DataciteTrackerComponent { this.setupDataciteViewTrackerEffect().subscribe(); } - protected override get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null> { + protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> { return this.registry$; } diff --git a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts index ff4733fdf..71d17256a 100644 --- a/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts +++ b/src/app/shared/components/datacite-tracker/datacite-tracker.component.ts @@ -2,7 +2,7 @@ import { filter, map, Observable, switchMap, take } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { ProjectIdentifiers } from '@osf/features/project/overview/models'; +import { Identifier } from '@shared/models'; import { DataciteService } from '@shared/services/datacite/datacite.service'; @Injectable() @@ -15,7 +15,7 @@ export abstract class DataciteTrackerComponent { * * @returns An Observable that emits an item which may contain DOI identifier or null . */ - protected abstract get trackable(): Observable<{ identifiers?: ProjectIdentifiers[] } | null>; + protected abstract get trackable(): Observable<{ identifiers?: Identifier[] } | null>; /** * Sets up a one-time effect to log a "view" event to Datacite for the resource DOI. diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.ts b/src/app/shared/components/resource-metadata/resource-metadata.component.ts index e4dce6506..bba444f89 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.ts +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.ts @@ -8,11 +8,12 @@ import { ChangeDetectionStrategy, Component, input, output } from '@angular/core import { RouterLink } from '@angular/router'; import { OverviewCollectionsComponent } from '@osf/features/project/overview/components/overview-collections/overview-collections.component'; -import { AffiliatedInstitutionsViewComponent, TruncatedTextComponent } from '@shared/components'; +import { AffiliatedInstitutionsViewComponent } from '@shared/components'; import { OsfResourceTypes } from '@shared/constants'; import { ResourceOverview } from '@shared/models'; import { ResourceCitationsComponent } from '../resource-citations/resource-citations.component'; +import { TruncatedTextComponent } from '../truncated-text/truncated-text.component'; // avoids circular imports @Component({ selector: 'osf-resource-metadata',