Skip to content

Island rhythms/task performance#167

Open
IslandRhythms wants to merge 5 commits intomainfrom
IslandRhythms/task-performance
Open

Island rhythms/task performance#167
IslandRhythms wants to merge 5 commits intomainfrom
IslandRhythms/task-performance

Conversation

@IslandRhythms
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
studio Ready Ready Preview, Comment Feb 27, 2026 9:19pm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the task management system to improve performance through pagination and optimized aggregation queries. The changes separate task overview data (status counts and tasks grouped by name) from detailed task lists, enabling more efficient data loading for large task sets.

Changes:

  • Introduced a new getTaskOverview backend action that uses MongoDB aggregation to efficiently compute status counts and task summaries by name
  • Refactored getTasks to support pagination with skip/limit parameters and return document counts
  • Added date range filtering and pagination UI to the task-by-name page with a fixed bottom navigation bar

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
backend/actions/Task/getTaskOverview.js New action that aggregates task overview data (status counts and tasks by name) using MongoDB's $facet
backend/actions/Task/getTasks.js Refactored to add pagination support with skip/limit parameters and return total document count
backend/actions/Task/index.js Exports the new getTaskOverview action
frontend/src/api.js Adds getTaskOverview API method for both Lambda and non-Lambda configurations
frontend/src/tasks/tasks.js Refactored to use getTaskOverview for status counts, removed 'All Time' filter, fixed date range handling
frontend/src/task-by-name/task-by-name.js Added pagination logic, date range filtering, and query parameter handling
frontend/src/task-by-name/task-by-name.html Added pagination UI with page controls and per-page size selector
frontend/src/tasks/task-details/task-details.js Added showBackButton prop to conditionally display back button
frontend/src/tasks/task-details/task-details.html Made back button conditional based on showBackButton prop
frontend/src/task-single/task-single.js Fixed indentation (formatting only)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you use $lt shouldn't this just be new Date(Date.UTC(y, m, 0, 0, 0, 0, 0)) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well don't we want to include the day?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Define all possible statuses
const allStatuses = ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'];
const skip = Math.max(0, Number(params.skip) || 0);
const limit = Math.min(MAX_LIMIT, Math.max(0, Number(params.limit) || 100));
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limit parsing uses Number(params.limit) || 100, which treats an explicit limit: 0 as falsy and silently replaces it with 100. If callers want limit: 0 (e.g. fetch counts only), this will return documents unexpectedly. Consider using a nullish check / Number.isFinite() instead of || so that 0 is preserved.

Suggested change
const limit = Math.min(MAX_LIMIT, Math.max(0, Number(params.limit) || 100));
const rawLimit = Number(params.limit);
const limit = Math.min(
MAX_LIMIT,
Math.max(0, Number.isFinite(rawLimit) ? rawLimit : 100)
);

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +12
skip: { $type: Number, $default: 0 },
limit: { $type: Number, $default: 100 }
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is the only place in backend/actions that uses Archetype number types as $type: Number. Elsewhere the codebase consistently uses $type: 'number' for numeric params. To avoid inconsistent behavior (and potential Archetype type parsing differences), align skip/limit to the same $type: 'number' pattern used in other actions.

Suggested change
skip: { $type: Number, $default: 0 },
limit: { $type: Number, $default: 100 }
skip: { $type: 'number', $default: 0 },
limit: { $type: 'number', $default: 100 }

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +88
$group: {
_id: '$name',
totalCount: { $sum: 1 },
lastRun: { $max: '$scheduledAt' },
pending: { $sum: { $cond: [{ $eq: ['$status', 'pending'] }, 1, 0] } },
succeeded: { $sum: { $cond: [{ $eq: ['$status', 'succeeded'] }, 1, 0] } },
failed: { $sum: { $cond: [{ $eq: ['$status', 'failed'] }, 1, 0] } },
cancelled: { $sum: { $cond: [{ $eq: ['$status', 'cancelled'] }, 1, 0] } }
}
},
{
$project: {
_id: 0,
name: '$_id',
totalCount: 1,
lastRun: 1,
statusCounts: {
pending: '$pending',
succeeded: '$succeeded',
failed: '$failed',
cancelled: '$cancelled'
}
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tasksByName computes totalCount as $sum: 1 across all matched tasks, but the per-name statusCounts only counts 4 statuses (pending/succeeded/failed/cancelled). If any tasks are in_progress or unknown, totalCount will exceed the sum of the displayed counts (the same mismatch called out earlier for the UI). Consider either (a) adding in_progress/unknown (or an “other”) count fields in the $group + statusCounts projection, or (b) changing the default match to only include OVERVIEW_STATUSES when no explicit status filter is provided so totals and statusCounts stay consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +66
end = new Date();
end.setHours(23, 59, 59, 999);
break;
case 'yesterday':
start = new Date(now);
start.setDate(start.getDate() - 1);
start.setHours(0, 0, 0, 0);
end = new Date(start);
end.setHours(23, 59, 59, 999);
break;
case 'thisWeek':
start = new Date(now.getTime() - (7 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date();
end.setHours(23, 59, 59, 999);
break;
case 'lastWeek':
start = new Date(now.getTime() - (14 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date(now.getTime() - (7 * 86400000));
end.setHours(23, 59, 59, 999);
break;
case 'thisMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m + 1, 0, 23, 59, 59, 999));
break;
}
case 'lastMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDateRangeForRange() sets end to 23:59:59.999 for day/week ranges, but the backend queries use { scheduledAt: { $lt: end } }. That makes the last millisecond of the intended range exclusive, and mixes an inclusive-style end with an exclusive $lt query. To make the boundary unambiguous, consider returning an exclusive end (start of the next day / next week range) when the backend uses $lt, or switch the backend to $lte when using 23:59:59.999 style ends.

Suggested change
end = new Date();
end.setHours(23, 59, 59, 999);
break;
case 'yesterday':
start = new Date(now);
start.setDate(start.getDate() - 1);
start.setHours(0, 0, 0, 0);
end = new Date(start);
end.setHours(23, 59, 59, 999);
break;
case 'thisWeek':
start = new Date(now.getTime() - (7 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date();
end.setHours(23, 59, 59, 999);
break;
case 'lastWeek':
start = new Date(now.getTime() - (14 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date(now.getTime() - (7 * 86400000));
end.setHours(23, 59, 59, 999);
break;
case 'thisMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m + 1, 0, 23, 59, 59, 999));
break;
}
case 'lastMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
end = new Date(start);
end.setDate(end.getDate() + 1);
end.setHours(0, 0, 0, 0);
break;
case 'yesterday':
start = new Date(now);
start.setDate(start.getDate() - 1);
start.setHours(0, 0, 0, 0);
end = new Date(start);
end.setDate(end.getDate() + 1);
end.setHours(0, 0, 0, 0);
break;
case 'thisWeek':
start = new Date(now.getTime() - (7 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date();
end.setDate(end.getDate() + 1);
end.setHours(0, 0, 0, 0);
break;
case 'lastWeek':
start = new Date(now.getTime() - (14 * 86400000));
start.setHours(0, 0, 0, 0);
end = new Date(now.getTime() - (7 * 86400000));
end.setHours(0, 0, 0, 0);
break;
case 'thisMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m + 1, 1, 0, 0, 0, 0));
break;
}
case 'lastMonth': {
const y = now.getUTCFullYear();
const m = now.getUTCMonth();
start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
end = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +185
const res = await actions.Task.getTasks({ limit: 10000 });
assert.strictEqual(Array.isArray(res.tasks), true);
assert.strictEqual(res.tasks.length, 0);
assert.strictEqual(res.numDocs, 0);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test caps limit at MAX_LIMIT (2000) doesn’t actually verify the cap: it calls getTasks({ limit: 10000 }) with an empty collection and only asserts tasks.length === 0. To cover the behavior, insert >2000 tasks and assert that tasks.length is 2000 (and numDocs reflects the full matching count).

Suggested change
const res = await actions.Task.getTasks({ limit: 10000 });
assert.strictEqual(Array.isArray(res.tasks), true);
assert.strictEqual(res.tasks.length, 0);
assert.strictEqual(res.numDocs, 0);
const totalTasks = 2100;
const docs = [];
const now = new Date();
for (let i = 0; i < totalTasks; i++) {
docs.push({ name: `task-${i}`, status: 'pending', scheduledAt: now });
}
await Task.insertMany(docs);
const res = await actions.Task.getTasks({ limit: 10000 });
assert.strictEqual(Array.isArray(res.tasks), true);
assert.strictEqual(res.tasks.length, 2000);
assert.strictEqual(res.numDocs, totalTasks);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants