Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/api/src/tasks/tasks.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ describe('TasksService', () => {
await expect(service.findById('does-not-exist')).resolves.toBeNull();
});

it('computes per-employee stats from the fixtures', () => {
it('computes per-employee stats from the fixtures', async () => {
const employeeId = Assignments[0].employee_id;
const expectedTotal = Assignments.filter(a => a.employee_id === employeeId).length;
const stats = service.getStatsForEmployee(employeeId);
const stats = await service.getStatsForEmployee(employeeId);
expect(stats.totalTasks).toBe(expectedTotal);
expect(stats.completionRate).toBeGreaterThanOrEqual(0);
});
Expand Down
10 changes: 9 additions & 1 deletion apps/api/src/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ export class TasksService {
return this.assignments.filter({ id }).first();
}

getStatsForEmployee(employeeId: string): ReturnType<AssignmentLookup['getStats']> {
async getStatsForEmployee(employeeId: string): Promise<ReturnType<AssignmentLookup['getStats']>> {
if (this.repo.enabled) {
// Same lookup math, hydrated from Postgres instead of the fixtures.
const [assignments, activities] = await Promise.all([
this.repo.listAssignments(employeeId),
this.repo.listActivities(employeeId),
]);
return new AssignmentLookup(assignments).getStats(employeeId, activities);
}
return this.assignments.getStats(employeeId);
}

Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ describe('UsersService', () => {
await expect(service.findById('does-not-exist')).resolves.toBeNull();
});

it('computes stats over the shared fixtures', () => {
const stats = service.getStats();
it('computes stats over the shared fixtures', async () => {
const stats = await service.getStats();
expect(stats.totalEmployees).toBe(Employees.length);
const employeeRole = stats.roles.find(r => r.role === 'employee');
expect(employeeRole?.count).toBe(Employees.filter(e => e.role === 'employee').length);
Expand Down
8 changes: 5 additions & 3 deletions apps/api/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ export class UsersService {
return this.employees.getById(id);
}

getStats(): ReturnType<EmployeeLookup['getStats']> {
// Stats still come from the in-memory lookup util; Postgres path returns the
// same shape over the loaded fixture until a SQL aggregate lands.
async getStats(): Promise<ReturnType<EmployeeLookup['getStats']>> {
if (this.repo.enabled) {
// Same lookup math, hydrated from Postgres instead of the fixtures.
return new EmployeeLookup(await this.repo.listEmployees()).getStats();
}
return this.employees.getStats();
}

Expand Down
8 changes: 8 additions & 0 deletions docs/handoffs/2026-07-26-api-postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ the same fixtures the offline demo uses.
- `EmployeeProfile.manager_id` is nullable; `Assignment.employee_id` /
`source_id` are UUIDs.

## DB-backed stats (follow-up branch `feat/api-db-stats`)

- `GET /users/stats` and `GET /tasks/stats/:employeeId` now hydrate the
`@worksight/common` lookup helpers from Postgres in DB mode, so stats
reflect live rows instead of always echoing fixtures.
`AssignmentLookup.getStats` gained an optional `activities` parameter
(defaults to the fixture set, so web callers are unaffected).

## Surveys (follow-up branch `feat/api-surveys`)

- Survey fixtures repaired: `SURVEY-123` → the real survey UUID, `EMP-001` →
Expand Down
9 changes: 6 additions & 3 deletions packages/common/src/utils/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,19 @@ export class AssignmentLookup extends BaseLookup<typeof AssignmentSchema> {
return this.filter({ employee_id, ...filters });
}

/** Compute stats for a given employee across tasks and activities */
getStats(employee_id: string) {
/**
* Compute stats for a given employee across tasks and activities.
* @param activities activity set to aggregate; defaults to the fixture data
*/
getStats(employee_id: string, activities: Activity[] = Activities) {
const employeeAssignments = this.filter({ employee_id });
const completedAssignments = employeeAssignments.filter({ status: 'completed' });

const totalStoryPoints = employeeAssignments.all().reduce((sum, t) => sum + (t.points ?? 0), 0);
const completedStoryPoints = completedAssignments.all().reduce((sum, t) => sum + (t.points ?? 0), 0);

// Activities stats
const employeeActivities = Activities.filter((a) => a.employee_id === employee_id);
const employeeActivities = activities.filter((a) => a.employee_id === employee_id);
const afterHours = employeeActivities.filter((a) => a.is_after_hours).length;
const weekend = employeeActivities.filter((a) => a.is_weekend).length;
const urgent = employeeActivities.filter((a) => a.is_urgent).length;
Expand Down
Loading