@taskforcehq/taskforce 0.3.306 → 0.3.307
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
|
|
2
2
|
import type { TaskActor } from '../shared/taskActor.js';
|
|
3
|
-
import type { Comment, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
|
|
3
|
+
import type { ActivityEntityType, Comment, EntityActivityItem, EntityEvent, TaskActivityItem, TaskEvent, TaskEventDetails } from './Taskforce.js';
|
|
4
4
|
export interface TaskActivityServiceDeps {
|
|
5
5
|
db: DatabaseAdapter;
|
|
6
6
|
tenantId: string;
|
|
@@ -9,9 +9,34 @@ export interface TaskActivityServiceDeps {
|
|
|
9
9
|
resolveWorkspaceActor: (workspaceId: string, actorRef: unknown) => TaskActor | null;
|
|
10
10
|
}
|
|
11
11
|
export declare function listWorkspaceTaskEventsService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
|
|
12
|
+
export declare function listWorkspaceEntityEventsService(deps: TaskActivityServiceDeps, workspaceId: string): EntityEvent[];
|
|
13
|
+
export declare function listWorkspaceEntityEventsForSyncService(deps: TaskActivityServiceDeps, workspaceId: string): EntityEvent[];
|
|
14
|
+
export declare function listWorkspaceTaskEventsForSyncService(deps: TaskActivityServiceDeps, workspaceId: string): TaskEvent[];
|
|
15
|
+
export declare function getTaskEventForSyncService(deps: TaskActivityServiceDeps, input: {
|
|
16
|
+
taskId: string;
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
eventId: string;
|
|
19
|
+
}): TaskEvent | null;
|
|
20
|
+
export declare function getEntityEventForSyncService(deps: TaskActivityServiceDeps, input: {
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
eventId: string;
|
|
23
|
+
}): EntityEvent | null;
|
|
12
24
|
export declare function replaceTaskCommentsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, comments: Comment[]): void;
|
|
25
|
+
export declare function listEntityEventsInternalService(deps: TaskActivityServiceDeps, entityType: ActivityEntityType, entityId: string, workspaceId: string): EntityEvent[];
|
|
13
26
|
export declare function listTaskEventsInternalService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string): TaskEvent[];
|
|
27
|
+
export declare function buildEntityActivityService(comments: Comment[], entityEvents: EntityEvent[], options?: {
|
|
28
|
+
hiddenActions?: string[];
|
|
29
|
+
}): EntityActivityItem[];
|
|
14
30
|
export declare function buildTaskActivityService(comments: Comment[], taskEvents: TaskEvent[]): TaskActivityItem[];
|
|
31
|
+
export declare function addEntityEventService(deps: TaskActivityServiceDeps, input: {
|
|
32
|
+
entityType: ActivityEntityType;
|
|
33
|
+
entityId: string;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
action: string;
|
|
36
|
+
actor: unknown;
|
|
37
|
+
createdAt?: string;
|
|
38
|
+
details?: TaskEventDetails;
|
|
39
|
+
}): void;
|
|
15
40
|
export declare function addTaskEventService(deps: TaskActivityServiceDeps, input: {
|
|
16
41
|
taskId: string;
|
|
17
42
|
workspaceId: string;
|
|
@@ -20,5 +45,7 @@ export declare function addTaskEventService(deps: TaskActivityServiceDeps, input
|
|
|
20
45
|
createdAt?: string;
|
|
21
46
|
details?: TaskEventDetails;
|
|
22
47
|
}): void;
|
|
48
|
+
export declare function replaceEntityEventsService(deps: TaskActivityServiceDeps, entityType: ActivityEntityType, entityId: string, workspaceId: string, entityEvents: EntityEvent[]): void;
|
|
23
49
|
export declare function replaceTaskEventsService(deps: TaskActivityServiceDeps, taskId: string, workspaceId: string, taskEvents: TaskEvent[]): void;
|
|
50
|
+
export declare function upsertEntityEventForSyncService(deps: TaskActivityServiceDeps, event: EntityEvent): EntityEvent;
|
|
24
51
|
export declare function upsertTaskEventForSyncService(deps: TaskActivityServiceDeps, event: TaskEvent): TaskEvent;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function
|
|
1
|
+
function normalizeEntityEventActor(deps, actorRef, workspaceId) {
|
|
2
2
|
const actorProfile = deps.resolveWorkspaceActor(workspaceId, actorRef);
|
|
3
3
|
const actor = String(actorRef || '').trim() || 'system';
|
|
4
4
|
if (actor === 'system') {
|
|
@@ -10,25 +10,48 @@ function normalizeTaskEventActor(deps, actorRef, workspaceId) {
|
|
|
10
10
|
actorProfile
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function normalizeEntityEventDetails(details) {
|
|
14
14
|
if (!details || typeof details !== 'object' || Array.isArray(details))
|
|
15
15
|
return {};
|
|
16
16
|
return details;
|
|
17
17
|
}
|
|
18
|
-
function
|
|
18
|
+
function normalizeEntityType(value) {
|
|
19
|
+
return value === 'initiative' || value === 'workstream' ? value : 'task';
|
|
20
|
+
}
|
|
21
|
+
function mapEntityEventRow(deps, row) {
|
|
19
22
|
const workspaceId = deps.normalizeWorkspaceId(typeof row.workspace_id === 'string' ? row.workspace_id : undefined);
|
|
20
23
|
const actor = String(row.actor || 'system').trim() || 'system';
|
|
24
|
+
const entityType = normalizeEntityType(row.entity_type);
|
|
25
|
+
const entityId = String(row.entity_id || row.task_id || '').trim();
|
|
21
26
|
let details = {};
|
|
22
27
|
try {
|
|
23
|
-
details =
|
|
28
|
+
details = normalizeEntityEventDetails(row.details_json ? JSON.parse(String(row.details_json)) : {});
|
|
24
29
|
}
|
|
25
30
|
catch {
|
|
26
31
|
details = {};
|
|
27
32
|
}
|
|
33
|
+
if (entityType === 'task') {
|
|
34
|
+
return {
|
|
35
|
+
id: String(row.id || '').trim(),
|
|
36
|
+
taskId: entityId,
|
|
37
|
+
workspaceId,
|
|
38
|
+
entityType,
|
|
39
|
+
entityId,
|
|
40
|
+
action: String(row.action || '').trim(),
|
|
41
|
+
actor,
|
|
42
|
+
actorType: row.actor_type === 'ai' || row.actor_type === 'human' || row.actor_type === 'system'
|
|
43
|
+
? row.actor_type
|
|
44
|
+
: 'system',
|
|
45
|
+
createdAt: String(row.created_at || ''),
|
|
46
|
+
details,
|
|
47
|
+
actorProfile: actor === 'system' ? null : deps.resolveWorkspaceActor(workspaceId, actor)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
28
50
|
return {
|
|
29
51
|
id: String(row.id || '').trim(),
|
|
30
|
-
taskId: String(row.task_id || '').trim(),
|
|
31
52
|
workspaceId,
|
|
53
|
+
entityType,
|
|
54
|
+
entityId,
|
|
32
55
|
action: String(row.action || '').trim(),
|
|
33
56
|
actor,
|
|
34
57
|
actorType: row.actor_type === 'ai' || row.actor_type === 'human' || row.actor_type === 'system'
|
|
@@ -39,16 +62,124 @@ function mapTaskEventRow(deps, row) {
|
|
|
39
62
|
actorProfile: actor === 'system' ? null : deps.resolveWorkspaceActor(workspaceId, actor)
|
|
40
63
|
};
|
|
41
64
|
}
|
|
65
|
+
function mapEntityEventSyncRow(deps, row) {
|
|
66
|
+
const workspaceId = deps.normalizeWorkspaceId(typeof row.workspace_id === 'string' ? row.workspace_id : undefined);
|
|
67
|
+
const entityType = normalizeEntityType(row.entity_type);
|
|
68
|
+
const entityId = String(row.entity_id || row.task_id || '').trim();
|
|
69
|
+
let details = {};
|
|
70
|
+
try {
|
|
71
|
+
details = normalizeEntityEventDetails(row.details_json ? JSON.parse(String(row.details_json)) : {});
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
details = {};
|
|
75
|
+
}
|
|
76
|
+
if (entityType === 'task') {
|
|
77
|
+
return {
|
|
78
|
+
id: String(row.id || '').trim(),
|
|
79
|
+
taskId: entityId,
|
|
80
|
+
workspaceId,
|
|
81
|
+
entityType: 'task',
|
|
82
|
+
entityId,
|
|
83
|
+
action: String(row.action || '').trim(),
|
|
84
|
+
actor: String(row.actor || 'system').trim() || 'system',
|
|
85
|
+
actorType: row.actor_type === 'ai' || row.actor_type === 'human' || row.actor_type === 'system'
|
|
86
|
+
? row.actor_type
|
|
87
|
+
: 'system',
|
|
88
|
+
createdAt: String(row.created_at || ''),
|
|
89
|
+
details,
|
|
90
|
+
// Sync only needs the stable event payload. Avoid actor hydration so
|
|
91
|
+
// large workspace snapshots don't blow up the Postgres worker buffer.
|
|
92
|
+
actorProfile: null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
id: String(row.id || '').trim(),
|
|
97
|
+
workspaceId,
|
|
98
|
+
entityType,
|
|
99
|
+
entityId,
|
|
100
|
+
action: String(row.action || '').trim(),
|
|
101
|
+
actor: String(row.actor || 'system').trim() || 'system',
|
|
102
|
+
actorType: row.actor_type === 'ai' || row.actor_type === 'human' || row.actor_type === 'system'
|
|
103
|
+
? row.actor_type
|
|
104
|
+
: 'system',
|
|
105
|
+
createdAt: String(row.created_at || ''),
|
|
106
|
+
details,
|
|
107
|
+
// Sync only needs the stable event payload. Avoid actor hydration so
|
|
108
|
+
// large workspace snapshots don't blow up the Postgres worker buffer.
|
|
109
|
+
actorProfile: null
|
|
110
|
+
};
|
|
111
|
+
}
|
|
42
112
|
export function listWorkspaceTaskEventsService(deps, workspaceId) {
|
|
43
113
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
44
114
|
const rows = deps.db.prepare(`
|
|
45
|
-
SELECT id,
|
|
46
|
-
FROM
|
|
115
|
+
SELECT id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
116
|
+
FROM entity_events
|
|
117
|
+
WHERE tenant_id = ?
|
|
118
|
+
AND workspace_id = ?
|
|
119
|
+
AND entity_type = 'task'
|
|
120
|
+
ORDER BY created_at ASC, id ASC
|
|
121
|
+
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
122
|
+
return rows.map((row) => mapEntityEventRow(deps, row));
|
|
123
|
+
}
|
|
124
|
+
export function listWorkspaceEntityEventsService(deps, workspaceId) {
|
|
125
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
126
|
+
const rows = deps.db.prepare(`
|
|
127
|
+
SELECT id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
128
|
+
FROM entity_events
|
|
129
|
+
WHERE tenant_id = ?
|
|
130
|
+
AND workspace_id = ?
|
|
131
|
+
ORDER BY created_at ASC, id ASC
|
|
132
|
+
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
133
|
+
return rows.map((row) => mapEntityEventRow(deps, row));
|
|
134
|
+
}
|
|
135
|
+
export function listWorkspaceEntityEventsForSyncService(deps, workspaceId) {
|
|
136
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
137
|
+
const rows = deps.db.prepare(`
|
|
138
|
+
SELECT id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
139
|
+
FROM entity_events
|
|
47
140
|
WHERE tenant_id = ?
|
|
48
141
|
AND workspace_id = ?
|
|
49
142
|
ORDER BY created_at ASC, id ASC
|
|
50
143
|
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
51
|
-
return rows.map((row) =>
|
|
144
|
+
return rows.map((row) => mapEntityEventSyncRow(deps, row));
|
|
145
|
+
}
|
|
146
|
+
export function listWorkspaceTaskEventsForSyncService(deps, workspaceId) {
|
|
147
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
148
|
+
const rows = deps.db.prepare(`
|
|
149
|
+
SELECT id, entity_id AS task_id, workspace_id, action, actor, actor_type, details_json, created_at
|
|
150
|
+
FROM entity_events
|
|
151
|
+
WHERE tenant_id = ?
|
|
152
|
+
AND workspace_id = ?
|
|
153
|
+
AND entity_type = 'task'
|
|
154
|
+
ORDER BY created_at ASC, id ASC
|
|
155
|
+
`).all(deps.tenantId, normalizedWorkspaceId);
|
|
156
|
+
return rows.map((row) => mapEntityEventSyncRow(deps, { ...row, entity_type: 'task', entity_id: row.task_id }));
|
|
157
|
+
}
|
|
158
|
+
export function getTaskEventForSyncService(deps, input) {
|
|
159
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
160
|
+
const row = deps.db.prepare(`
|
|
161
|
+
SELECT id, entity_id AS task_id, workspace_id, action, actor, actor_type, details_json, created_at
|
|
162
|
+
FROM entity_events
|
|
163
|
+
WHERE tenant_id = ?
|
|
164
|
+
AND workspace_id = ?
|
|
165
|
+
AND entity_type = 'task'
|
|
166
|
+
AND entity_id = ?
|
|
167
|
+
AND id = ?
|
|
168
|
+
LIMIT 1
|
|
169
|
+
`).get(deps.tenantId, normalizedWorkspaceId, String(input.taskId || '').trim(), String(input.eventId || '').trim());
|
|
170
|
+
return row ? mapEntityEventSyncRow(deps, { ...row, entity_type: 'task', entity_id: row.task_id }) : null;
|
|
171
|
+
}
|
|
172
|
+
export function getEntityEventForSyncService(deps, input) {
|
|
173
|
+
const normalizedWorkspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
174
|
+
const row = deps.db.prepare(`
|
|
175
|
+
SELECT id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
176
|
+
FROM entity_events
|
|
177
|
+
WHERE tenant_id = ?
|
|
178
|
+
AND workspace_id = ?
|
|
179
|
+
AND id = ?
|
|
180
|
+
LIMIT 1
|
|
181
|
+
`).get(deps.tenantId, normalizedWorkspaceId, String(input.eventId || '').trim());
|
|
182
|
+
return row ? mapEntityEventSyncRow(deps, row) : null;
|
|
52
183
|
}
|
|
53
184
|
export function replaceTaskCommentsService(deps, taskId, workspaceId, comments) {
|
|
54
185
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
@@ -73,19 +204,24 @@ export function replaceTaskCommentsService(deps, taskId, workspaceId, comments)
|
|
|
73
204
|
});
|
|
74
205
|
}
|
|
75
206
|
}
|
|
76
|
-
export function
|
|
207
|
+
export function listEntityEventsInternalService(deps, entityType, entityId, workspaceId) {
|
|
77
208
|
const rows = deps.db.prepare(`
|
|
78
|
-
SELECT id,
|
|
79
|
-
FROM
|
|
209
|
+
SELECT id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
210
|
+
FROM entity_events
|
|
80
211
|
WHERE tenant_id = ?
|
|
81
212
|
AND workspace_id = ?
|
|
82
|
-
AND
|
|
213
|
+
AND entity_type = ?
|
|
214
|
+
AND entity_id = ?
|
|
83
215
|
ORDER BY created_at ASC, id ASC
|
|
84
|
-
`).all(deps.tenantId, workspaceId,
|
|
85
|
-
return rows.map((row) =>
|
|
216
|
+
`).all(deps.tenantId, workspaceId, entityType, entityId);
|
|
217
|
+
return rows.map((row) => mapEntityEventRow(deps, row));
|
|
86
218
|
}
|
|
87
|
-
export function
|
|
88
|
-
|
|
219
|
+
export function listTaskEventsInternalService(deps, taskId, workspaceId) {
|
|
220
|
+
return listEntityEventsInternalService(deps, 'task', taskId, workspaceId);
|
|
221
|
+
}
|
|
222
|
+
export function buildEntityActivityService(comments, entityEvents, options) {
|
|
223
|
+
const hiddenActions = new Set(options?.hiddenActions || []);
|
|
224
|
+
const visibleEvents = entityEvents.filter((event) => !hiddenActions.has(event.action));
|
|
89
225
|
return [
|
|
90
226
|
...comments.map((comment) => ({
|
|
91
227
|
id: `comment:${comment.id}`,
|
|
@@ -93,7 +229,7 @@ export function buildTaskActivityService(comments, taskEvents) {
|
|
|
93
229
|
timestamp: comment.timestamp,
|
|
94
230
|
comment
|
|
95
231
|
})),
|
|
96
|
-
...
|
|
232
|
+
...visibleEvents.map((event) => ({
|
|
97
233
|
id: `event:${event.id}`,
|
|
98
234
|
type: 'event',
|
|
99
235
|
timestamp: event.createdAt,
|
|
@@ -108,68 +244,113 @@ export function buildTaskActivityService(comments, taskEvents) {
|
|
|
108
244
|
return String(left.id || '').localeCompare(String(right.id || ''));
|
|
109
245
|
});
|
|
110
246
|
}
|
|
111
|
-
export function
|
|
247
|
+
export function buildTaskActivityService(comments, taskEvents) {
|
|
248
|
+
return buildEntityActivityService(comments, taskEvents, {
|
|
249
|
+
hiddenActions: ['task-comment-added']
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
export function addEntityEventService(deps, input) {
|
|
112
253
|
const action = String(input.action || '').trim();
|
|
113
254
|
if (!action)
|
|
114
255
|
return;
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
256
|
+
const entityId = String(input.entityId || '').trim();
|
|
257
|
+
if (!entityId)
|
|
117
258
|
return;
|
|
259
|
+
const entityType = normalizeEntityType(input.entityType);
|
|
118
260
|
const workspaceId = deps.normalizeWorkspaceId(input.workspaceId);
|
|
119
261
|
const createdAt = String(input.createdAt || '').trim() || new Date().toISOString();
|
|
120
|
-
const { actor, actorType } =
|
|
262
|
+
const { actor, actorType } = normalizeEntityEventActor(deps, input.actor, workspaceId);
|
|
121
263
|
deps.db.prepare(`
|
|
122
|
-
INSERT INTO
|
|
123
|
-
id, tenant_id, workspace_id,
|
|
124
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
125
|
-
`).run(deps.generateEntityId('
|
|
264
|
+
INSERT INTO entity_events (
|
|
265
|
+
id, tenant_id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
266
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
267
|
+
`).run(deps.generateEntityId('entity-event'), deps.tenantId, workspaceId, entityType, entityId, action, actor, actorType, JSON.stringify(normalizeEntityEventDetails(input.details)), createdAt);
|
|
126
268
|
}
|
|
127
|
-
export function
|
|
269
|
+
export function addTaskEventService(deps, input) {
|
|
270
|
+
addEntityEventService(deps, {
|
|
271
|
+
entityType: 'task',
|
|
272
|
+
entityId: input.taskId,
|
|
273
|
+
workspaceId: input.workspaceId,
|
|
274
|
+
action: input.action,
|
|
275
|
+
actor: input.actor,
|
|
276
|
+
createdAt: input.createdAt,
|
|
277
|
+
details: input.details,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
export function replaceEntityEventsService(deps, entityType, entityId, workspaceId, entityEvents) {
|
|
128
281
|
const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
|
|
129
|
-
deps.db.prepare('DELETE FROM
|
|
130
|
-
.run(deps.tenantId, normalizedWorkspaceId,
|
|
131
|
-
if (
|
|
282
|
+
deps.db.prepare('DELETE FROM entity_events WHERE tenant_id = ? AND workspace_id = ? AND entity_type = ? AND entity_id = ?')
|
|
283
|
+
.run(deps.tenantId, normalizedWorkspaceId, entityType, entityId);
|
|
284
|
+
if (entityEvents.length === 0)
|
|
132
285
|
return;
|
|
133
|
-
const
|
|
134
|
-
INSERT INTO
|
|
135
|
-
id, tenant_id, workspace_id,
|
|
136
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
286
|
+
const insertEntityEvent = deps.db.prepare(`
|
|
287
|
+
INSERT INTO entity_events (
|
|
288
|
+
id, tenant_id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
289
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
137
290
|
`);
|
|
138
|
-
for (const event of
|
|
139
|
-
|
|
291
|
+
for (const event of entityEvents) {
|
|
292
|
+
insertEntityEvent.run(event.id, deps.tenantId, normalizedWorkspaceId, event.entityType, event.entityId, event.action, event.actor, event.actorType, JSON.stringify(normalizeEntityEventDetails(event.details)), event.createdAt);
|
|
140
293
|
}
|
|
141
294
|
}
|
|
142
|
-
export function
|
|
295
|
+
export function replaceTaskEventsService(deps, taskId, workspaceId, taskEvents) {
|
|
296
|
+
replaceEntityEventsService(deps, 'task', taskId, workspaceId, taskEvents.map((event) => ({
|
|
297
|
+
...event,
|
|
298
|
+
entityType: 'task',
|
|
299
|
+
entityId: String(event.entityId || event.taskId || taskId).trim(),
|
|
300
|
+
taskId: String(event.taskId || taskId).trim(),
|
|
301
|
+
})));
|
|
302
|
+
}
|
|
303
|
+
export function upsertEntityEventForSyncService(deps, event) {
|
|
143
304
|
const workspaceId = deps.normalizeWorkspaceId(event.workspaceId);
|
|
305
|
+
const entityType = normalizeEntityType(event.entityType);
|
|
144
306
|
const normalizedEvent = {
|
|
145
307
|
id: String(event.id || '').trim(),
|
|
146
|
-
taskId: String(event.taskId || '').trim(),
|
|
147
308
|
workspaceId,
|
|
309
|
+
entityType,
|
|
310
|
+
entityId: String(event.entityId || (entityType === 'task' ? event.taskId : '') || '').trim(),
|
|
148
311
|
action: String(event.action || '').trim(),
|
|
149
312
|
actor: String(event.actor || 'system').trim() || 'system',
|
|
150
313
|
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
151
314
|
? event.actorType
|
|
152
315
|
: 'system',
|
|
153
316
|
createdAt: String(event.createdAt || '').trim() || new Date().toISOString(),
|
|
154
|
-
details:
|
|
317
|
+
details: normalizeEntityEventDetails(event.details)
|
|
155
318
|
};
|
|
156
319
|
deps.db.prepare(`
|
|
157
|
-
INSERT INTO
|
|
158
|
-
id, tenant_id, workspace_id,
|
|
159
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
320
|
+
INSERT INTO entity_events (
|
|
321
|
+
id, tenant_id, workspace_id, entity_type, entity_id, action, actor, actor_type, details_json, created_at
|
|
322
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
160
323
|
ON CONFLICT (id) DO UPDATE SET
|
|
161
324
|
workspace_id = excluded.workspace_id,
|
|
162
|
-
|
|
325
|
+
entity_type = excluded.entity_type,
|
|
326
|
+
entity_id = excluded.entity_id,
|
|
163
327
|
action = excluded.action,
|
|
164
328
|
actor = excluded.actor,
|
|
165
329
|
actor_type = excluded.actor_type,
|
|
166
330
|
details_json = excluded.details_json,
|
|
167
331
|
created_at = excluded.created_at
|
|
168
|
-
`).run(normalizedEvent.id, deps.tenantId, workspaceId, normalizedEvent.
|
|
332
|
+
`).run(normalizedEvent.id, deps.tenantId, workspaceId, normalizedEvent.entityType, normalizedEvent.entityId, normalizedEvent.action, normalizedEvent.actor, normalizedEvent.actorType, JSON.stringify(normalizedEvent.details), normalizedEvent.createdAt);
|
|
333
|
+
const actorProfile = normalizedEvent.actor === 'system'
|
|
334
|
+
? null
|
|
335
|
+
: deps.resolveWorkspaceActor(workspaceId, normalizedEvent.actor);
|
|
336
|
+
if (normalizedEvent.entityType === 'task') {
|
|
337
|
+
return {
|
|
338
|
+
...normalizedEvent,
|
|
339
|
+
entityType: 'task',
|
|
340
|
+
taskId: normalizedEvent.entityId,
|
|
341
|
+
actorProfile,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
169
344
|
return {
|
|
170
345
|
...normalizedEvent,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: deps.resolveWorkspaceActor(workspaceId, normalizedEvent.actor)
|
|
346
|
+
entityType: normalizedEvent.entityType,
|
|
347
|
+
actorProfile,
|
|
174
348
|
};
|
|
175
349
|
}
|
|
350
|
+
export function upsertTaskEventForSyncService(deps, event) {
|
|
351
|
+
return upsertEntityEventForSyncService(deps, {
|
|
352
|
+
...event,
|
|
353
|
+
entityType: 'task',
|
|
354
|
+
entityId: event.taskId,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { TaskforceCore } from './Taskforce';
|
|
6
|
+
function makeCore() {
|
|
7
|
+
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-activity-sync-'));
|
|
8
|
+
const core = new TaskforceCore({
|
|
9
|
+
projectRoot,
|
|
10
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
core,
|
|
14
|
+
cleanup: () => {
|
|
15
|
+
core.close();
|
|
16
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe('TaskforceCore sync activity reads', () => {
|
|
21
|
+
it('lists workspace entity events for sync without hydrating actor profiles', () => {
|
|
22
|
+
const { core, cleanup } = makeCore();
|
|
23
|
+
try {
|
|
24
|
+
const task = core.createTask({ title: 'Sync task event seed', createdBy: 'user-1' }, {
|
|
25
|
+
workspaceId: 'workspace-a',
|
|
26
|
+
actorRef: 'user-1'
|
|
27
|
+
});
|
|
28
|
+
const initiative = core.createInitiative({ title: 'Initiative seed' }, 'workspace-a', {
|
|
29
|
+
actorRef: 'user-1'
|
|
30
|
+
});
|
|
31
|
+
core.recordTaskEvent({
|
|
32
|
+
taskId: task.id,
|
|
33
|
+
workspaceId: 'workspace-a',
|
|
34
|
+
action: 'task-updated',
|
|
35
|
+
actor: 'user-1',
|
|
36
|
+
createdAt: '2026-04-28T10:00:00.000Z',
|
|
37
|
+
details: { changes: { assignee: { from: 'user-1', to: 'ai-1' } } }
|
|
38
|
+
});
|
|
39
|
+
core.updateInitiative(initiative.id, { description: 'Updated initiative body' }, 'workspace-a', {
|
|
40
|
+
actorRef: 'user-1'
|
|
41
|
+
});
|
|
42
|
+
vi.spyOn(core, 'resolveWorkspaceActor').mockImplementation(() => {
|
|
43
|
+
throw new Error('sync snapshot should not hydrate actor profiles');
|
|
44
|
+
});
|
|
45
|
+
const events = core.listEntityEventsForSync({ workspaceId: 'workspace-a' });
|
|
46
|
+
expect(events.map((event) => event.action)).toEqual(expect.arrayContaining([
|
|
47
|
+
'task-created',
|
|
48
|
+
'task-updated',
|
|
49
|
+
'initiative-created',
|
|
50
|
+
'initiative-updated',
|
|
51
|
+
]));
|
|
52
|
+
expect(events.every((event) => event.actorProfile === null)).toBe(true);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
cleanup();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
it('looks up one entity event for sync without hydrating actor profiles', () => {
|
|
59
|
+
const { core, cleanup } = makeCore();
|
|
60
|
+
try {
|
|
61
|
+
const workstream = core.createWorkstream({ title: 'Single sync event seed' }, 'workspace-a', {
|
|
62
|
+
actorRef: 'user-1'
|
|
63
|
+
});
|
|
64
|
+
core.updateWorkstream(workstream.id, {
|
|
65
|
+
description: 'Workstream moved forward'
|
|
66
|
+
}, 'workspace-a', {
|
|
67
|
+
actorRef: 'user-1'
|
|
68
|
+
});
|
|
69
|
+
const target = core.getWorkstream(workstream.id, 'workspace-a')?.entityEvents?.find((event) => event.action === 'workstream-updated');
|
|
70
|
+
expect(target).toBeTruthy();
|
|
71
|
+
vi.spyOn(core, 'resolveWorkspaceActor').mockImplementation(() => {
|
|
72
|
+
throw new Error('single sync lookup should not hydrate actor profiles');
|
|
73
|
+
});
|
|
74
|
+
const syncEvent = core.getEntityEventForSync({
|
|
75
|
+
workspaceId: 'workspace-a',
|
|
76
|
+
eventId: target.id,
|
|
77
|
+
});
|
|
78
|
+
expect(syncEvent).toEqual(expect.objectContaining({
|
|
79
|
+
id: target.id,
|
|
80
|
+
entityType: 'workstream',
|
|
81
|
+
entityId: workstream.id,
|
|
82
|
+
action: 'workstream-updated',
|
|
83
|
+
actorProfile: null,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
cleanup();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -183,6 +183,102 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
183
183
|
cleanup();
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
|
+
it('records initiative and workstream activity events and exposes them on hydrated planning entities', () => {
|
|
187
|
+
const { core, cleanup } = makeCore();
|
|
188
|
+
try {
|
|
189
|
+
core.createWorkspace({ workspaceId: 'workspace-a', name: 'Workspace A' });
|
|
190
|
+
const initiative = core.createInitiative({ title: 'Planning initiative' }, 'workspace-a', { actorRef: 'user' });
|
|
191
|
+
const nestedInitiative = core.createInitiative({ title: 'Nested initiative' }, 'workspace-a', { actorRef: 'user' });
|
|
192
|
+
const workstream = core.createWorkstream({
|
|
193
|
+
title: 'Planning workstream',
|
|
194
|
+
initiativeId: initiative.id,
|
|
195
|
+
}, 'workspace-a', { actorRef: 'user' });
|
|
196
|
+
const siblingWorkstream = core.createWorkstream({
|
|
197
|
+
title: 'Sibling workstream',
|
|
198
|
+
initiativeId: initiative.id,
|
|
199
|
+
}, 'workspace-a', { actorRef: 'user' });
|
|
200
|
+
core.updateInitiative(initiative.id, {
|
|
201
|
+
description: 'Refined initiative description',
|
|
202
|
+
attachments: [{ path: 'https://example.com/initiative-spec.md', caption: 'initiative spec' }],
|
|
203
|
+
}, 'workspace-a', { actorRef: 'user' });
|
|
204
|
+
core.archiveInitiative(initiative.id, 'workspace-a', { actorRef: 'user' });
|
|
205
|
+
core.unarchiveInitiative(initiative.id, 'workspace-a', { actorRef: 'user' });
|
|
206
|
+
core.updateWorkstream(workstream.id, {
|
|
207
|
+
initiativeId: nestedInitiative.id,
|
|
208
|
+
}, 'workspace-a', { actorRef: 'user' });
|
|
209
|
+
core.archiveWorkstream(workstream.id, 'workspace-a', { actorRef: 'user' });
|
|
210
|
+
core.unarchiveWorkstream(workstream.id, 'workspace-a', { actorRef: 'user' });
|
|
211
|
+
const linkedTask = core.createTask({
|
|
212
|
+
title: 'Linked task',
|
|
213
|
+
createdBy: 'user',
|
|
214
|
+
workstreamId: workstream.id,
|
|
215
|
+
}, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
216
|
+
core.updateTask(linkedTask.id, { workstreamId: siblingWorkstream.id }, 'workspace-a', { actorRef: 'user' });
|
|
217
|
+
core.updateTask(linkedTask.id, { workstreamId: null }, 'workspace-a', { actorRef: 'user' });
|
|
218
|
+
const hydratedInitiative = core.getInitiative(initiative.id, 'workspace-a');
|
|
219
|
+
const hydratedWorkstream = core.getWorkstream(workstream.id, 'workspace-a');
|
|
220
|
+
const hydratedSiblingWorkstream = core.getWorkstream(siblingWorkstream.id, 'workspace-a');
|
|
221
|
+
const initiativeActions = (hydratedInitiative?.activity || [])
|
|
222
|
+
.filter((item) => item.type === 'event')
|
|
223
|
+
.map((item) => item.event.action);
|
|
224
|
+
const workstreamRelationshipEvent = (hydratedWorkstream?.activity || [])
|
|
225
|
+
.filter((item) => item.type === 'event')
|
|
226
|
+
.map((item) => item.event)
|
|
227
|
+
.find((event) => event.action === 'workstream-relationship-changed');
|
|
228
|
+
const workstreamActions = (hydratedWorkstream?.activity || [])
|
|
229
|
+
.filter((item) => item.type === 'event')
|
|
230
|
+
.map((item) => item.event.action);
|
|
231
|
+
const siblingWorkstreamActions = (hydratedSiblingWorkstream?.activity || [])
|
|
232
|
+
.filter((item) => item.type === 'event')
|
|
233
|
+
.map((item) => item.event.action);
|
|
234
|
+
const workstreamTaskAddedEvent = (hydratedWorkstream?.activity || [])
|
|
235
|
+
.filter((item) => item.type === 'event')
|
|
236
|
+
.map((item) => item.event)
|
|
237
|
+
.find((event) => event.action === 'workstream-task-added');
|
|
238
|
+
const workstreamTaskRemovedEvent = (hydratedWorkstream?.activity || [])
|
|
239
|
+
.filter((item) => item.type === 'event')
|
|
240
|
+
.map((item) => item.event)
|
|
241
|
+
.find((event) => event.action === 'workstream-task-removed');
|
|
242
|
+
expect(initiativeActions).toEqual(expect.arrayContaining([
|
|
243
|
+
'initiative-created',
|
|
244
|
+
'initiative-updated',
|
|
245
|
+
'initiative-archived',
|
|
246
|
+
'initiative-unarchived',
|
|
247
|
+
]));
|
|
248
|
+
expect(workstreamActions).toEqual(expect.arrayContaining([
|
|
249
|
+
'workstream-created',
|
|
250
|
+
'workstream-relationship-changed',
|
|
251
|
+
'workstream-task-added',
|
|
252
|
+
'workstream-task-removed',
|
|
253
|
+
'workstream-archived',
|
|
254
|
+
'workstream-unarchived',
|
|
255
|
+
]));
|
|
256
|
+
expect(siblingWorkstreamActions).toEqual(expect.arrayContaining([
|
|
257
|
+
'workstream-created',
|
|
258
|
+
'workstream-task-added',
|
|
259
|
+
'workstream-task-removed',
|
|
260
|
+
]));
|
|
261
|
+
expect(workstreamRelationshipEvent?.details?.changes?.initiativeId).toEqual({
|
|
262
|
+
from: initiative.id,
|
|
263
|
+
to: nestedInitiative.id,
|
|
264
|
+
});
|
|
265
|
+
expect(workstreamTaskAddedEvent?.details?.taskId).toBe(linkedTask.id);
|
|
266
|
+
expect(workstreamTaskAddedEvent?.details?.taskTitle).toBe('Linked task');
|
|
267
|
+
expect(workstreamTaskAddedEvent?.details?.changes?.workstreamId).toEqual({
|
|
268
|
+
from: null,
|
|
269
|
+
to: workstream.id,
|
|
270
|
+
});
|
|
271
|
+
expect(workstreamTaskRemovedEvent?.details?.changes?.workstreamId).toEqual({
|
|
272
|
+
from: workstream.id,
|
|
273
|
+
to: siblingWorkstream.id,
|
|
274
|
+
});
|
|
275
|
+
expect(hydratedInitiative?.entityEvents?.every((event) => event.entityType === 'initiative')).toBe(true);
|
|
276
|
+
expect(hydratedWorkstream?.entityEvents?.every((event) => event.entityType === 'workstream')).toBe(true);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
cleanup();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
186
282
|
it('repairs legacy comments tables so raw submitted authors can be stored', () => {
|
|
187
283
|
const { core, cleanup } = makeCore();
|
|
188
284
|
try {
|