diff --git a/src/app/features/registry/pages/registration-recent-activity/registration-recent-activity.component.spec.ts b/src/app/features/registry/pages/registration-recent-activity/registration-recent-activity.component.spec.ts
index bc5504bbd..6105ced38 100644
--- a/src/app/features/registry/pages/registration-recent-activity/registration-recent-activity.component.spec.ts
+++ b/src/app/features/registry/pages/registration-recent-activity/registration-recent-activity.component.spec.ts
@@ -13,7 +13,7 @@ import { RecentActivityListComponent } from '@osf/shared/components/recent-activ
import { CurrentResourceType } from '@osf/shared/enums/resource-type.enum';
import { ActivityLogsSelectors, ClearActivityLogs } from '@osf/shared/stores/activity-logs';
-import { MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log-with-display.mock';
+import { MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log.mock';
import { provideOSFCore } from '@testing/osf.testing.provider';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { BaseSetupOverrides, mergeSignalOverrides, provideMockStore } from '@testing/providers/store-provider.mock';
diff --git a/src/app/shared/components/recent-activity/recent-activity-list.component.spec.ts b/src/app/shared/components/recent-activity/recent-activity-list.component.spec.ts
index 31e850f46..f5ee9bbad 100644
--- a/src/app/shared/components/recent-activity/recent-activity-list.component.spec.ts
+++ b/src/app/shared/components/recent-activity/recent-activity-list.component.spec.ts
@@ -6,10 +6,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
-import {
- makeActivityLogWithDisplay,
- MOCK_ACTIVITY_LOGS_WITH_DISPLAY,
-} from '@testing/mocks/activity-log-with-display.mock';
+import { makeActivityLogWithDisplay, MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log.mock';
import { provideOSFCore } from '@testing/osf.testing.provider';
import { RecentActivityListComponent } from './recent-activity-list.component';
diff --git a/src/app/shared/mappers/activity-logs.mapper.ts b/src/app/shared/mappers/activity-logs.mapper.ts
index ba0caf8ea..ec75f8b44 100644
--- a/src/app/shared/mappers/activity-logs.mapper.ts
+++ b/src/app/shared/mappers/activity-logs.mapper.ts
@@ -16,6 +16,7 @@ export class ActivityLogsMapper {
type: log.type,
action: log.attributes.action,
date: log.attributes.date,
+ foreignUser: log.attributes.foreign_user,
params: {
contributors: contributors.length
? contributors.map((contributor) => this.fromContributorJsonApi(contributor))
diff --git a/src/app/shared/models/activity-logs/activity-logs-json-api.model.ts b/src/app/shared/models/activity-logs/activity-logs-json-api.model.ts
index a63d50129..6265ba8ee 100644
--- a/src/app/shared/models/activity-logs/activity-logs-json-api.model.ts
+++ b/src/app/shared/models/activity-logs/activity-logs-json-api.model.ts
@@ -4,6 +4,7 @@ export interface ActivityLogJsonApi {
attributes: {
action: string;
date: string;
+ foreign_user: string | null;
params: {
contributors?: LogContributorJsonApi[];
license?: string;
diff --git a/src/app/shared/models/activity-logs/activity-logs.model.ts b/src/app/shared/models/activity-logs/activity-logs.model.ts
index 1b729eaf0..1de7efa46 100644
--- a/src/app/shared/models/activity-logs/activity-logs.model.ts
+++ b/src/app/shared/models/activity-logs/activity-logs.model.ts
@@ -6,6 +6,7 @@ export interface ActivityLog {
type: string;
action: string;
date: string;
+ foreignUser: string | null;
params: {
contributors: LogContributor[];
license?: string;
diff --git a/src/app/shared/services/activity-logs/activity-log-formatter.service.spec.ts b/src/app/shared/services/activity-logs/activity-log-formatter.service.spec.ts
new file mode 100644
index 000000000..d0f9d1204
--- /dev/null
+++ b/src/app/shared/services/activity-logs/activity-log-formatter.service.spec.ts
@@ -0,0 +1,310 @@
+import { TranslateService } from '@ngx-translate/core';
+
+import { TestBed } from '@angular/core/testing';
+
+import { makeActivityLog } from '@testing/mocks/activity-log.mock';
+import { provideOSFCore } from '@testing/osf.testing.provider';
+
+import { ActivityLogFormatterService } from './activity-log-formatter.service';
+import { ActivityLogUrlBuilderService } from './activity-log-url-builder.service';
+
+describe('ActivityLogFormatterService', () => {
+ let service: ActivityLogFormatterService;
+ let translateService: TranslateService;
+ let urlBuilder: ActivityLogUrlBuilderService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [ActivityLogFormatterService, ActivityLogUrlBuilderService, provideOSFCore()],
+ });
+
+ service = TestBed.inject(ActivityLogFormatterService);
+ translateService = TestBed.inject(TranslateService);
+ urlBuilder = TestBed.inject(ActivityLogUrlBuilderService);
+ });
+
+ it('buildAnonymous returns anonymousAn when anonymous link is set', () => {
+ const log = makeActivityLog({ params: { anonymousLink: true } });
+
+ expect(service.buildAnonymous(log)).toBe('activityLog.defaults.anonymousAn');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.anonymousAn');
+ });
+
+ it('buildAnonymous returns anonymousA when anonymous link is not set', () => {
+ const log = makeActivityLog();
+
+ expect(service.buildAnonymous(log)).toBe('activityLog.defaults.anonymousA');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.anonymousA');
+ });
+
+ it('buildUser returns user url when embedded user is available', () => {
+ const log = makeActivityLog({
+ embeds: {
+ user: {
+ id: 'user-1',
+ type: 'users',
+ fullName: 'Jane Doe',
+ givenName: 'Jane',
+ middleNames: '',
+ familyName: 'Doe',
+ suffix: '',
+ dateRegistered: '2024-01-01T00:00:00Z',
+ active: true,
+ timezone: 'UTC',
+ locale: 'en',
+ },
+ },
+ });
+
+ expect(service.buildUser(log)).toBe('Jane Doe');
+ });
+
+ it('buildUser capitalizes foreignUser when no user url is available', () => {
+ const log = makeActivityLog({ foreignUser: 'githubUser' });
+
+ expect(service.buildUser(log)).toBe('GithubUser');
+ });
+
+ it('buildUser returns default translation when user and foreignUser are missing', () => {
+ const log = makeActivityLog();
+
+ expect(service.buildUser(log)).toBe('activityLog.defaults.aUser');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.aUser');
+ });
+
+ it('buildCommentLocation returns file translation when file param exists', () => {
+ const buildAHrefElementSpy = vi.spyOn(urlBuilder, 'buildAHrefElement');
+ const log = makeActivityLog({
+ params: {
+ file: { name: 'data.csv', url: 'files/data.csv' },
+ },
+ });
+
+ expect(service.buildCommentLocation(log)).toBe('activityLog.defaults.fileOn');
+ expect(buildAHrefElementSpy).toHaveBeenCalledWith('/files/data.csv', 'data.csv');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.fileOn', {
+ file: 'data.csv',
+ });
+ });
+
+ it('buildCommentLocation returns wiki translation when wiki param exists', () => {
+ const log = makeActivityLog({
+ params: {
+ wiki: { name: 'Home', url: 'wiki/home' },
+ },
+ });
+
+ expect(service.buildCommentLocation(log)).toBe('activityLog.defaults.wikiOn');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.wikiOn', {
+ wiki: 'Home',
+ });
+ });
+
+ it('buildCommentLocation returns empty string when file and wiki are missing', () => {
+ expect(service.buildCommentLocation(makeActivityLog())).toBe('');
+ });
+
+ it('buildContributorsList returns someUsers when contributors are missing', () => {
+ expect(service.buildContributorsList(makeActivityLog())).toBe('activityLog.defaults.someUsers');
+ });
+
+ it('buildContributorsList returns linked active contributors and others count', () => {
+ const log = makeActivityLog({
+ params: {
+ contributors: [
+ {
+ id: 'c-1',
+ fullName: 'Alice',
+ givenName: 'Alice',
+ middleNames: '',
+ familyName: '',
+ unregisteredName: null,
+ active: true,
+ },
+ {
+ id: 'c-2',
+ fullName: 'Bob',
+ givenName: 'Bob',
+ middleNames: '',
+ familyName: '',
+ unregisteredName: null,
+ active: true,
+ },
+ {
+ id: 'c-3',
+ fullName: 'Carol',
+ givenName: 'Carol',
+ middleNames: '',
+ familyName: '',
+ unregisteredName: null,
+ active: true,
+ },
+ {
+ id: 'c-4',
+ fullName: 'Dave',
+ givenName: 'Dave',
+ middleNames: '',
+ familyName: '',
+ unregisteredName: null,
+ active: false,
+ },
+ {
+ id: 'c-5',
+ fullName: 'Eve',
+ givenName: 'Eve',
+ middleNames: '',
+ familyName: '',
+ unregisteredName: null,
+ active: false,
+ },
+ ],
+ },
+ });
+
+ expect(service.buildContributorsList(log)).toBe(
+ 'Alice, Bob, CarolactivityLog.defaults.contributorsAnd 2activityLog.defaults.contributorsOthers'
+ );
+ });
+
+ it('buildIdentifiers returns empty string when identifiers are missing', () => {
+ expect(service.buildIdentifiers(makeActivityLog())).toBe('');
+ });
+
+ it('buildIdentifiers returns doi and ark when both exist', () => {
+ const log = makeActivityLog({
+ params: {
+ identifiers: { doi: '10.1234/example', ark: 'ark:/12345' },
+ },
+ });
+
+ expect(service.buildIdentifiers(log)).toBe('doi:10.1234/example and ark:ark:/12345');
+ });
+
+ it('buildIdentifiers returns only doi when ark is missing', () => {
+ const log = makeActivityLog({
+ params: {
+ identifiers: { doi: '10.1234/example' },
+ },
+ });
+
+ expect(service.buildIdentifiers(log)).toBe('doi:10.1234/example');
+ });
+
+ it('buildPathType returns folder for paths starting with slash', () => {
+ const log = makeActivityLog({ params: { path: '/folder' } });
+
+ expect(service.buildPathType(log)).toBe('activityLog.defaults.folder');
+ });
+
+ it('buildPathType returns file for non-folder paths', () => {
+ const log = makeActivityLog({ params: { path: 'file.txt' } });
+
+ expect(service.buildPathType(log)).toBe('activityLog.defaults.file');
+ });
+
+ it('buildPathType returns empty string when path is missing', () => {
+ expect(service.buildPathType(makeActivityLog())).toBe('');
+ });
+
+ it('buildVersion returns version from params or hash fallback', () => {
+ expect(service.buildVersion(makeActivityLog({ params: { version: '3' } }))).toBe('3');
+ expect(service.buildVersion(makeActivityLog())).toBe('#');
+ });
+
+ it('buildUpdatedFields returns field translation when updated fields are missing', () => {
+ expect(service.buildUpdatedFields(makeActivityLog())).toBe('activityLog.defaults.field');
+ });
+
+ it('buildUpdatedFields translates known category values', () => {
+ const log = makeActivityLog({
+ params: {
+ updatedFields: {
+ category: { old: 'other', new: 'analysis' },
+ },
+ },
+ });
+
+ expect(service.buildUpdatedFields(log)).toBe('activityLog.defaults.updatedFields');
+ expect(translateService.instant).toHaveBeenCalledWith('nodeCategories.analysis');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.updatedFields', {
+ old: 'category',
+ new: 'nodeCategories.analysis',
+ });
+ });
+
+ it('buildUpdatedFields returns raw new value for non-category fields', () => {
+ const log = makeActivityLog({
+ params: {
+ updatedFields: {
+ title: { old: 'Old', new: 'New' },
+ },
+ },
+ });
+
+ expect(service.buildUpdatedFields(log)).toBe('activityLog.defaults.updatedFields');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.updatedFields', {
+ old: 'title',
+ new: 'New',
+ });
+ });
+
+ it('getPointerCategory returns linked node category', () => {
+ const log = makeActivityLog({
+ embeds: {
+ linkedNode: {
+ id: 'node-1',
+ type: 'nodes',
+ title: 'Linked',
+ description: '',
+ category: 'project',
+ customCitation: null,
+ dateCreated: '2024-01-01T00:00:00Z',
+ dateModified: '2024-01-01T00:00:00Z',
+ registration: false,
+ preprint: false,
+ fork: false,
+ collection: false,
+ tags: [],
+ accessRequestsEnabled: false,
+ nodeLicense: { copyrightHolders: [], year: null },
+ currentUserCanComment: false,
+ currentUserPermissions: [],
+ currentUserIsContributor: false,
+ currentUserIsContributorOrGroupMember: false,
+ wikiEnabled: false,
+ public: true,
+ subjects: [],
+ },
+ },
+ });
+
+ expect(service.getPointerCategory(log)).toBe('project');
+ });
+
+ it('buildFallbackMessage uses fallbackWithNode when node url exists', () => {
+ const log = makeActivityLog({
+ action: 'node_updated',
+ params: {
+ paramsNode: { id: 'node-1', title: 'My Project' },
+ },
+ });
+
+ expect(service.buildFallbackMessage(log)).toBe('activityLog.defaults.fallbackWithNode');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.fallbackWithNode', {
+ user: 'activityLog.defaults.aUser',
+ action: 'node updated',
+ node: 'My Project',
+ });
+ });
+
+ it('buildFallbackMessage uses fallbackWithoutNode when node is empty', () => {
+ const log = makeActivityLog({ action: 'node_updated' });
+ vi.spyOn(service, 'buildNode').mockReturnValue('');
+
+ expect(service.buildFallbackMessage(log)).toBe('activityLog.defaults.fallbackWithoutNode');
+ expect(translateService.instant).toHaveBeenCalledWith('activityLog.defaults.fallbackWithoutNode', {
+ user: 'activityLog.defaults.aUser',
+ action: 'node updated',
+ });
+ });
+});
diff --git a/src/app/shared/services/activity-logs/activity-log-formatter.service.ts b/src/app/shared/services/activity-logs/activity-log-formatter.service.ts
index 7e3d026dc..f7f0d5f01 100644
--- a/src/app/shared/services/activity-logs/activity-log-formatter.service.ts
+++ b/src/app/shared/services/activity-logs/activity-log-formatter.service.ts
@@ -222,7 +222,16 @@ export class ActivityLogFormatterService {
buildUser(log: ActivityLog): string {
const userUrl = this.urlBuilder.buildUserUrl(log);
- return userUrl || this.translateService.instant('activityLog.defaults.aUser');
+
+ if (userUrl) {
+ return userUrl;
+ }
+
+ if (log.foreignUser) {
+ return this.capitalizeFirst(log.foreignUser);
+ }
+
+ return this.translateService.instant('activityLog.defaults.aUser');
}
buildNode(log: ActivityLog): string {
@@ -278,4 +287,8 @@ export class ActivityLogFormatterService {
private replaceSlash(path: string): string {
return path.replace(/^\/|\/$/g, '');
}
+
+ private capitalizeFirst(value: string): string {
+ return value.charAt(0).toUpperCase() + value.slice(1);
+ }
}
diff --git a/src/app/shared/services/activity-logs/activity-logs.service.spec.ts b/src/app/shared/services/activity-logs/activity-logs.service.spec.ts
index ba94bb4bd..8cef5b2b6 100644
--- a/src/app/shared/services/activity-logs/activity-logs.service.spec.ts
+++ b/src/app/shared/services/activity-logs/activity-logs.service.spec.ts
@@ -5,11 +5,7 @@ import { inject, TestBed } from '@angular/core/testing';
import { CurrentResourceType } from '@osf/shared/enums/resource-type.enum';
-import {
- buildNodeLogsUrl,
- buildRegistrationLogsUrl,
- getActivityLogsResponse,
-} from '@testing/data/activity-logs/activity-logs.data';
+import { buildNodeLogsUrl, buildRegistrationLogsUrl, getActivityLogsResponse } from '@testing/mocks/activity-log.mock';
import { provideOSFCore, provideOSFHttp } from '@testing/osf.testing.provider';
import { ActivityLogDisplayServiceMock } from '@testing/providers/activity-log-display.service.mock';
import { EnvironmentTokenMock } from '@testing/providers/environment.token.mock';
diff --git a/src/app/shared/stores/activity-logs/activity-logs.selectors.spec.ts b/src/app/shared/stores/activity-logs/activity-logs.selectors.spec.ts
index 572ccfa9e..77c79eaca 100644
--- a/src/app/shared/stores/activity-logs/activity-logs.selectors.spec.ts
+++ b/src/app/shared/stores/activity-logs/activity-logs.selectors.spec.ts
@@ -6,10 +6,7 @@ import { TestBed } from '@angular/core/testing';
import { ActivityLogDisplayService } from '@osf/shared/services/activity-logs/activity-log-display.service';
-import {
- makeActivityLogWithDisplay,
- MOCK_ACTIVITY_LOGS_WITH_DISPLAY,
-} from '@testing/mocks/activity-log-with-display.mock';
+import { makeActivityLogWithDisplay, MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log.mock';
import { provideOSFHttp } from '@testing/osf.testing.provider';
import { ActivityLogDisplayServiceMock } from '@testing/providers/activity-log-display.service.mock';
diff --git a/src/app/shared/stores/activity-logs/activity-logs.state.spec.ts b/src/app/shared/stores/activity-logs/activity-logs.state.spec.ts
index ac801700a..f3d110d04 100644
--- a/src/app/shared/stores/activity-logs/activity-logs.state.spec.ts
+++ b/src/app/shared/stores/activity-logs/activity-logs.state.spec.ts
@@ -10,7 +10,7 @@ import { CurrentResourceType } from '@shared/enums/resource-type.enum';
import { PaginatedData } from '@shared/models/paginated-data.model';
import { ActivityLogsService } from '@shared/services/activity-logs/activity-logs.service';
-import { MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log-with-display.mock';
+import { MOCK_ACTIVITY_LOGS_WITH_DISPLAY } from '@testing/mocks/activity-log.mock';
import { ClearActivityLogs, GetActivityLogs } from './activity-logs.actions';
import { ACTIVITY_LOGS_STATE_DEFAULT } from './activity-logs.model';
diff --git a/src/testing/data/activity-logs/activity-logs.data.ts b/src/testing/data/activity-logs/activity-logs.data.ts
deleted file mode 100644
index 5726607eb..000000000
--- a/src/testing/data/activity-logs/activity-logs.data.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-export const ACTIVITY_LOGS_EMBEDS_QS =
- 'embed%5B%5D=original_node&embed%5B%5D=user&embed%5B%5D=linked_node&embed%5B%5D=linked_registration&embed%5B%5D=template_node';
-
-export function buildRegistrationLogsUrl(registrationId: string, page: number, pageSize: number, apiBase: string) {
- return `${apiBase}/v2/registrations/${registrationId}/logs/?${ACTIVITY_LOGS_EMBEDS_QS}&page=${page}&page%5Bsize%5D=${pageSize}`;
-}
-
-export function buildNodeLogsUrl(projectId: string, page: number, pageSize: number, apiBase: string) {
- return `${apiBase}/v2/nodes/${projectId}/logs/?${ACTIVITY_LOGS_EMBEDS_QS}&page=${page}&page%5Bsize%5D=${pageSize}`;
-}
-
-type AnyObj = Record;
-
-export function makeActivityLog(overrides: AnyObj = {}) {
- return structuredClone({
- id: 'log1',
- type: 'logs',
- attributes: {
- action: 'update',
- date: '2024-01-01T00:00:00Z',
- params: {},
- },
- embeds: {},
- ...overrides,
- });
-}
-
-export function makeActivityLogsResponse(logs: AnyObj[] = [], total?: number) {
- const data = logs.length
- ? logs
- : [
- makeActivityLog(),
- makeActivityLog({
- id: 'log2',
- attributes: { action: 'create', date: '2024-01-02T00:00:00Z', params: {} },
- }),
- ];
-
- return structuredClone({
- data,
- meta: {
- total: total ?? data.length,
- anonymous: false,
- },
- included: null,
- });
-}
-
-export function getActivityLogsResponse() {
- return makeActivityLogsResponse();
-}
diff --git a/src/testing/mocks/activity-log-with-display.mock.ts b/src/testing/mocks/activity-log-with-display.mock.ts
deleted file mode 100644
index a64c8b367..000000000
--- a/src/testing/mocks/activity-log-with-display.mock.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { ActivityLogWithDisplay } from '@osf/shared/models/activity-logs/activity-log-with-display.model';
-
-export function makeActivityLogWithDisplay(overrides: Partial = {}): ActivityLogWithDisplay {
- return structuredClone({
- id: 'log1',
- type: 'logs',
- action: 'update',
- date: '2024-01-01T00:00:00Z',
- params: {
- contributors: [],
- paramsNode: { id: 'node1', title: 'Test Node' },
- paramsProject: null,
- pointer: null,
- },
- formattedActivity: 'Test activity',
- ...overrides,
- });
-}
-
-export const MOCK_ACTIVITY_LOGS_WITH_DISPLAY: ActivityLogWithDisplay[] = [
- makeActivityLogWithDisplay({
- id: 'log1',
- action: 'update',
- date: '2024-01-01T00:00:00Z',
- formattedActivity: 'Test activity 1',
- }),
- makeActivityLogWithDisplay({
- id: 'log2',
- action: 'create',
- date: '2024-01-02T00:00:00Z',
- params: {
- contributors: [],
- paramsNode: { id: 'node2', title: 'Test Node 2' },
- paramsProject: null,
- pointer: null,
- },
- formattedActivity: 'Test activity 2',
- }),
-];
diff --git a/src/testing/mocks/activity-log.mock.ts b/src/testing/mocks/activity-log.mock.ts
new file mode 100644
index 000000000..8388183c3
--- /dev/null
+++ b/src/testing/mocks/activity-log.mock.ts
@@ -0,0 +1,118 @@
+import { ActivityLogWithDisplay } from '@osf/shared/models/activity-logs/activity-log-with-display.model';
+import { ActivityLog } from '@osf/shared/models/activity-logs/activity-logs.model';
+
+export const ACTIVITY_LOGS_EMBEDS_QS =
+ 'embed%5B%5D=original_node&embed%5B%5D=user&embed%5B%5D=linked_node&embed%5B%5D=linked_registration&embed%5B%5D=template_node';
+
+export function buildRegistrationLogsUrl(registrationId: string, page: number, pageSize: number, apiBase: string) {
+ return `${apiBase}/v2/registrations/${registrationId}/logs/?${ACTIVITY_LOGS_EMBEDS_QS}&page=${page}&page%5Bsize%5D=${pageSize}`;
+}
+
+export function buildNodeLogsUrl(projectId: string, page: number, pageSize: number, apiBase: string) {
+ return `${apiBase}/v2/nodes/${projectId}/logs/?${ACTIVITY_LOGS_EMBEDS_QS}&page=${page}&page%5Bsize%5D=${pageSize}`;
+}
+
+type ActivityLogJsonApiOverrides = Record;
+
+export function makeActivityLogJsonApi(overrides: ActivityLogJsonApiOverrides = {}) {
+ return structuredClone({
+ id: 'log1',
+ type: 'logs',
+ attributes: {
+ action: 'update',
+ date: '2024-01-01T00:00:00Z',
+ params: {},
+ },
+ embeds: {},
+ ...overrides,
+ });
+}
+
+export function makeActivityLogsResponse(logs: ActivityLogJsonApiOverrides[] = [], total?: number) {
+ const data = logs.length
+ ? logs
+ : [
+ makeActivityLogJsonApi(),
+ makeActivityLogJsonApi({
+ id: 'log2',
+ attributes: { action: 'create', date: '2024-01-02T00:00:00Z', params: {} },
+ }),
+ ];
+
+ return structuredClone({
+ data,
+ meta: {
+ total: total ?? data.length,
+ anonymous: false,
+ },
+ included: null,
+ });
+}
+
+export function getActivityLogsResponse() {
+ return makeActivityLogsResponse();
+}
+
+export type ActivityLogOverrides = Partial> & {
+ params?: Partial;
+};
+
+export function makeActivityLog(overrides: ActivityLogOverrides = {}): ActivityLog {
+ const { params: paramsOverrides, ...restOverrides } = overrides;
+
+ return structuredClone({
+ id: 'log-1',
+ type: 'logs',
+ action: 'node_updated',
+ date: '2024-01-01T00:00:00Z',
+ foreignUser: null,
+ params: {
+ contributors: [],
+ paramsNode: { id: '', title: '' },
+ paramsProject: null,
+ pointer: null,
+ ...paramsOverrides,
+ },
+ ...restOverrides,
+ });
+}
+
+export type ActivityLogWithDisplayOverrides = ActivityLogOverrides & {
+ formattedActivity?: ActivityLogWithDisplay['formattedActivity'];
+};
+
+export function makeActivityLogWithDisplay(overrides: ActivityLogWithDisplayOverrides = {}): ActivityLogWithDisplay {
+ const { params: paramsOverrides, ...restOverrides } = overrides;
+ const formattedActivity = 'formattedActivity' in overrides ? overrides.formattedActivity : 'Test activity';
+
+ return {
+ ...makeActivityLog({
+ id: 'log1',
+ action: 'update',
+ params: {
+ paramsNode: { id: 'node1', title: 'Test Node' },
+ ...paramsOverrides,
+ },
+ ...restOverrides,
+ }),
+ formattedActivity,
+ };
+}
+
+export const MOCK_ACTIVITY_LOGS_WITH_DISPLAY: ActivityLogWithDisplay[] = [
+ makeActivityLogWithDisplay({
+ id: 'log1',
+ action: 'update',
+ date: '2024-01-01T00:00:00Z',
+ formattedActivity: 'Test activity 1',
+ }),
+ makeActivityLogWithDisplay({
+ id: 'log2',
+ action: 'create',
+ date: '2024-01-02T00:00:00Z',
+ params: {
+ paramsNode: { id: 'node2', title: 'Test Node 2' },
+ },
+ formattedActivity: 'Test activity 2',
+ }),
+];