Skip to content

Commit 52c5896

Browse files
authored
Feat(8653): Implement view tracking for registrations and preprints (#308)
* feat(datacite-tracker): implemented datacite view tracking for registries and preprints * chore(datacite-tracker): refactored doi extraction to be less repetitive * fix(datacite-tracker): reverted undesired refactor * chore(datacite-tracker): added tests to registry component * fix(datacite-tracker): fixed datacite tracker effect * chore(datacite-tracker): added tests to project and preprint components
1 parent 4026407 commit 52c5896

15 files changed

Lines changed: 385 additions & 48 deletions

jest.config.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = {
88
'^@osf/(.*)$': '<rootDir>/src/app/$1',
99
'^@core/(.*)$': '<rootDir>/src/app/core/$1',
1010
'^@shared/(.*)$': '<rootDir>/src/app/shared/$1',
11-
'^@styles/(.*)$': '<rootDir>/src/styles/$1',
11+
'^@styles/(.*)$': '<rootDir>/assets/styles/$1',
1212
'^@testing/(.*)$': '<rootDir>/src/testing/$1',
1313
'^src/environments/environment$': '<rootDir>/src/environments/environment.ts',
1414
},
@@ -62,16 +62,16 @@ module.exports = {
6262
testPathIgnorePatterns: [
6363
'<rootDir>/src/app/app.config.ts',
6464
'<rootDir>/src/app/app.routes.ts',
65-
'<rootDir>/src/app/features/registry/',
6665
'<rootDir>/src/app/features/project/addons/components/configure-configure-addon/',
6766
'<rootDir>/src/app/features/project/addons/components/connect-configured-addon/',
6867
'<rootDir>/src/app/features/project/addons/components/disconnect-addon-modal/',
6968
'<rootDir>/src/app/features/project/addons/components/confirm-account-connection-modal/',
7069
'<rootDir>/src/app/features/files/',
7170
'<rootDir>/src/app/features/my-projects/',
72-
'<rootDir>/src/app/features/preprints/',
71+
'<rootDir>/src/app/features/project/analytics/',
7372
'<rootDir>/src/app/features/project/contributors/',
74-
'<rootDir>/src/app/features/project/overview/',
73+
'<rootDir>/src/app/features/project/files/',
74+
'<rootDir>/src/app/features/project/metadata/',
7575
'<rootDir>/src/app/features/project/registrations',
7676
'<rootDir>/src/app/features/project/settings',
7777
'<rootDir>/src/app/features/project/wiki',
@@ -87,6 +87,9 @@ module.exports = {
8787
'<rootDir>/src/app/shared/components/pie-chart/',
8888
'<rootDir>/src/app/shared/components/resource-citations/',
8989
'<rootDir>/src/app/shared/components/reusable-filter/',
90+
'<rootDir>/src/app/shared/components/shared-metadata/dialogs/affiliated-institutions-dialog/',
91+
'<rootDir>/src/app/shared/components/shared-metadata/dialogs/contributors-dialog/',
92+
'<rootDir>/src/app/shared/components/shared-metadata/shared-metadata',
9093
'<rootDir>/src/app/shared/components/subjects/',
9194
'<rootDir>/src/app/shared/components/wiki/edit-section/',
9295
'<rootDir>/src/app/shared/components/wiki/wiki-list/',

src/app/features/preprints/mappers/preprint-providers.mapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class PreprintProvidersMapper {
3131
backgroundColor: brandRaw.attributes.background_color,
3232
},
3333
iri: response.links.iri,
34-
faviconUrl: response.attributes.assets.favicon,
34+
faviconUrl: response.attributes.assets?.favicon,
3535
squareColorNoTransparentImageUrl: response.attributes.assets?.square_color_no_transparent,
3636
reviewsWorkflow: response.attributes.reviews_workflow,
3737
facebookAppId: response.attributes.facebook_app_id,

src/app/features/preprints/mappers/preprints.mapper.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ export class PreprintsMapper {
136136
views: meta.metrics.views,
137137
},
138138
embeddedLicense: LicensesMapper.fromLicenseDataJsonApi(data.embeds.license.data),
139+
identifiers: data.embeds.identifiers?.data.map((identifier) => ({
140+
id: identifier.id,
141+
type: identifier.type,
142+
value: identifier.attributes.value,
143+
category: identifier.attributes.category,
144+
})),
139145
preprintDoiLink: links.preprint_doi,
140146
articleDoiLink: links.doi,
141147
};

src/app/features/preprints/models/preprint-json-api.models.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ export interface PreprintEmbedsJsonApi {
6969
data: ContributorResponse[];
7070
};
7171
license: LicenseResponseJsonApi;
72+
identifiers: {
73+
data: {
74+
id: string;
75+
type: string;
76+
attributes: {
77+
category: string;
78+
value: string;
79+
};
80+
}[];
81+
};
7282
}
7383

7484
export interface PreprintMetaJsonApi {

src/app/features/preprints/models/preprint.models.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { UserPermissions } from '@osf/shared/enums';
2-
import { BooleanOrNull, StringOrNull } from '@osf/shared/helpers';
3-
import { IdName, License, LicenseOptions } from '@osf/shared/models';
1+
import { Identifier, IdName, License, LicenseOptions } from '@osf/shared/models';
2+
import { UserPermissions } from '@shared/enums';
3+
import { BooleanOrNull, StringOrNull } from '@shared/helpers';
44

55
import { ApplicabilityStatus, PreregLinkInfo, ReviewsState } from '../enums';
66

@@ -43,6 +43,7 @@ export interface Preprint {
4343
embeddedLicense?: License;
4444
preprintDoiLink?: string;
4545
articleDoiLink?: string;
46+
identifiers?: Identifier[];
4647
}
4748

4849
export interface PreprintFilesLinks {

src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { MockComponents, MockPipe, MockProvider } from 'ng-mocks';
55

66
import { of } from 'rxjs';
77

8+
import { signal } from '@angular/core';
89
import { ComponentFixture, TestBed } from '@angular/core/testing';
10+
import { provideNoopAnimations } from '@angular/platform-browser/animations';
911
import { ActivatedRoute, Router } from '@angular/router';
1012

1113
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
1517
import { PreprintSelectors } from '@osf/features/preprints/store/preprint';
1618
import { PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers';
1719
import { MOCK_PROVIDER, MOCK_STORE, TranslateServiceMock } from '@shared/mocks';
20+
import { Identifier } from '@shared/models';
21+
import { DataciteService } from '@shared/services/datacite/datacite.service';
1822

1923
import { PreprintDetailsComponent } from './preprint-details.component';
2024

21-
describe.skip('PreprintDetailsComponent', () => {
25+
describe('PreprintDetailsComponent', () => {
2226
let component: PreprintDetailsComponent;
2327
let fixture: ComponentFixture<PreprintDetailsComponent>;
2428

29+
let dataciteService: jest.Mocked<DataciteService>;
30+
31+
const preprintSignal = signal<any | null>({ id: 'p1', title: 'Test', description: '' });
2532
const mockRoute: Partial<ActivatedRoute> = {
2633
params: of({ providerId: 'osf', preprintId: 'p1' }),
34+
queryParams: of({ providerId: 'osf', preprintId: 'p1' }),
2735
};
2836

2937
beforeEach(async () => {
@@ -34,13 +42,17 @@ describe.skip('PreprintDetailsComponent', () => {
3442
case PreprintProvidersSelectors.isPreprintProviderDetailsLoading:
3543
return () => false;
3644
case PreprintSelectors.getPreprint:
37-
return () => ({ id: 'p1', title: 'Test', description: '' });
45+
return preprintSignal;
3846
case PreprintSelectors.isPreprintLoading:
3947
return () => false;
4048
default:
4149
return () => [];
4250
}
4351
});
52+
(MOCK_STORE.dispatch as jest.Mock).mockImplementation(() => of());
53+
dataciteService = {
54+
logView: jest.fn().mockReturnValue(of(void 0)),
55+
} as unknown as jest.Mocked<DataciteService>;
4456

4557
await TestBed.configureTestingModule({
4658
imports: [
@@ -55,6 +67,8 @@ describe.skip('PreprintDetailsComponent', () => {
5567
],
5668
providers: [
5769
MockProvider(Store, MOCK_STORE),
70+
provideNoopAnimations(),
71+
{ provide: DataciteService, useValue: dataciteService },
5872
MockProvider(Router),
5973
MockProvider(ActivatedRoute, mockRoute),
6074
TranslateServiceMock,
@@ -66,11 +80,36 @@ describe.skip('PreprintDetailsComponent', () => {
6680
fixture.detectChanges();
6781
});
6882

69-
it('should create', () => {
70-
expect(component).toBeTruthy();
71-
});
72-
7383
it('isOsfPreprint should be true if providerId === osf', () => {
7484
expect(component.isOsfPreprint()).toBeTruthy();
7585
});
86+
87+
it('reacts to sequence of state changes', () => {
88+
fixture.detectChanges();
89+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
90+
91+
preprintSignal.set(getPreprint([]));
92+
93+
fixture.detectChanges();
94+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
95+
96+
preprintSignal.set(getPreprint([{ category: 'dio', value: '123', id: '', type: 'identifier' }]));
97+
fixture.detectChanges();
98+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
99+
100+
preprintSignal.set(getPreprint([{ category: 'doi', value: '123', id: '', type: 'identifier' }]));
101+
102+
fixture.detectChanges();
103+
expect(dataciteService.logView).toHaveBeenCalled();
104+
105+
preprintSignal.set(getPreprint([{ category: 'doi', value: '456', id: '', type: 'identifier' }]));
106+
fixture.detectChanges();
107+
expect(dataciteService.logView).toHaveBeenLastCalledWith('123');
108+
});
76109
});
110+
111+
function getPreprint(identifiers: Identifier[]) {
112+
return {
113+
identifiers: identifiers,
114+
};
115+
}

src/app/features/preprints/pages/preprint-details/preprint-details.component.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Button } from 'primeng/button';
66
import { DialogService } from 'primeng/dynamicdialog';
77
import { Skeleton } from 'primeng/skeleton';
88

9-
import { filter, map, of } from 'rxjs';
9+
import { filter, map, Observable, of } from 'rxjs';
1010

1111
import { DatePipe, Location } from '@angular/common';
1212
import {
@@ -19,7 +19,7 @@ import {
1919
OnDestroy,
2020
OnInit,
2121
} from '@angular/core';
22-
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
22+
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
2323
import { ActivatedRoute, Router } from '@angular/router';
2424

2525
import { UserSelectors } from '@core/store/user';
@@ -46,7 +46,9 @@ import {
4646
import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers';
4747
import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper';
4848
import { IS_MEDIUM, pathJoin } from '@osf/shared/helpers';
49+
import { DataciteTrackerComponent } from '@shared/components/datacite-tracker/datacite-tracker.component';
4950
import { ReviewPermissions, UserPermissions } from '@shared/enums';
51+
import { Identifier } from '@shared/models';
5052
import { MetaTagsService } from '@shared/services';
5153
import { ContributorsSelectors } from '@shared/stores';
5254

@@ -75,7 +77,7 @@ import { environment } from 'src/environments/environment';
7577
providers: [DialogService, DatePipe],
7678
changeDetection: ChangeDetectionStrategy.OnPush,
7779
})
78-
export class PreprintDetailsComponent implements OnInit, OnDestroy {
80+
export class PreprintDetailsComponent extends DataciteTrackerComponent implements OnInit, OnDestroy {
7981
@HostBinding('class') classes = 'flex-1 flex flex-column w-full';
8082

8183
private readonly router = inject(Router);
@@ -105,6 +107,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
105107
preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId()));
106108
isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading);
107109
preprint = select(PreprintSelectors.getPreprint);
110+
preprint$ = toObservable(select(PreprintSelectors.getPreprint));
108111
isPreprintLoading = select(PreprintSelectors.isPreprintLoading);
109112
contributors = select(ContributorsSelectors.getContributors);
110113
areContributorsLoading = select(ContributorsSelectors.isContributorsLoading);
@@ -281,12 +284,17 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
281284
this.fetchPreprint(this.preprintId());
282285
},
283286
});
287+
this.setupDataciteViewTrackerEffect().subscribe();
284288
}
285289

286290
ngOnDestroy() {
287291
this.actions.resetState();
288292
}
289293

294+
protected override get trackable(): Observable<{ identifiers?: Identifier[] } | null> {
295+
return this.preprint$;
296+
}
297+
290298
fetchPreprintVersion(preprintVersionId: string) {
291299
const currentUrl = this.router.url;
292300
const newUrl = currentUrl.replace(/[^/]+$/, preprintVersionId);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class PreprintsService {
7777
const params = {
7878
'metrics[views]': 'total',
7979
'metrics[downloads]': 'total',
80-
'embed[]': 'license',
80+
'embed[]': ['license', 'identifiers'],
8181
};
8282
return this.jsonApiService
8383
.get<

0 commit comments

Comments
 (0)