mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(core): Correct Agent session pagination on SQLite (#39802)
This commit is contained in:
parent
8d4111dff8
commit
1882cff76b
|
|
@ -146,6 +146,11 @@ export class AgentExecutionThreadRepository extends BaseRepository<AgentExecutio
|
|||
cursor?: string,
|
||||
filters: AgentSessionQueryFilters = {},
|
||||
): Promise<AgentExecutionThreadPage> {
|
||||
// SQLite timestamps can omit milliseconds, so compare them in one format.
|
||||
const updatedAt =
|
||||
this.manager.connection.options.type === 'postgres'
|
||||
? 'thread.updatedAt'
|
||||
: "STRFTIME('%Y-%m-%d %H:%M:%f', thread.updatedAt)";
|
||||
const query = this.createQueryBuilder('thread')
|
||||
.where('thread.projectId = :projectId', { projectId })
|
||||
.andWhere('thread.agentId = :agentId', { agentId })
|
||||
|
|
@ -157,9 +162,9 @@ export class AgentExecutionThreadRepository extends BaseRepository<AgentExecutio
|
|||
.take(limit + 1);
|
||||
|
||||
if (cursor) {
|
||||
query.andWhere('thread.updatedAt < :cursor', { cursor: new Date(cursor) });
|
||||
query.andWhere(`${updatedAt} < :cursor`, { cursor: new Date(cursor) });
|
||||
}
|
||||
this.applyListFilters(query, filters, userId);
|
||||
this.applyListFilters(query, filters, userId, updatedAt);
|
||||
const threads = await query.getMany();
|
||||
const hasMore = threads.length > limit;
|
||||
if (hasMore) threads.pop();
|
||||
|
|
@ -174,17 +179,18 @@ export class AgentExecutionThreadRepository extends BaseRepository<AgentExecutio
|
|||
query: SelectQueryBuilder<AgentExecutionThread>,
|
||||
filters: AgentSessionQueryFilters,
|
||||
userId: string,
|
||||
updatedAt: string,
|
||||
) {
|
||||
if (filters.scope === 'mine') {
|
||||
query.andWhere('thread.ownerId = :userId', { userId });
|
||||
}
|
||||
if (filters.updatedAfter) {
|
||||
query.andWhere('thread.updatedAt >= :updatedAfter', {
|
||||
query.andWhere(`${updatedAt} >= :updatedAfter`, {
|
||||
updatedAfter: filters.updatedAfter,
|
||||
});
|
||||
}
|
||||
if (filters.updatedBefore) {
|
||||
query.andWhere('thread.updatedAt <= :updatedBefore', {
|
||||
query.andWhere(`${updatedAt} <= :updatedBefore`, {
|
||||
updatedBefore: filters.updatedBefore,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2304,6 +2304,56 @@ describe('AgentExecutionRepository', () => {
|
|||
return await repository.save(execution);
|
||||
};
|
||||
|
||||
it('paginates sessions with whole-second and millisecond timestamps', async () => {
|
||||
const newest = await createThread();
|
||||
const middle = await createThread();
|
||||
const oldest = await createThread();
|
||||
for (const [id, timestamp] of [
|
||||
[newest.id, '2026-01-03 00:00:01'],
|
||||
[middle.id, '2026-01-03 00:00:00.500'],
|
||||
[oldest.id, '2026-01-03 00:00:00'],
|
||||
]) {
|
||||
await threadRepo
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({ updatedAt: () => `'${timestamp}'` })
|
||||
.where('id = :id', { id })
|
||||
.execute();
|
||||
}
|
||||
|
||||
const first = await threadRepo.findByProjectIdPaginated(projectId, agentId, viewerId, 1);
|
||||
const second = await threadRepo.findByProjectIdPaginated(
|
||||
projectId,
|
||||
agentId,
|
||||
viewerId,
|
||||
1,
|
||||
first.nextCursor ?? undefined,
|
||||
);
|
||||
const third = await threadRepo.findByProjectIdPaginated(
|
||||
projectId,
|
||||
agentId,
|
||||
viewerId,
|
||||
1,
|
||||
second.nextCursor ?? undefined,
|
||||
);
|
||||
const after = await threadRepo.findByProjectIdPaginated(
|
||||
projectId,
|
||||
agentId,
|
||||
viewerId,
|
||||
10,
|
||||
undefined,
|
||||
{
|
||||
updatedAfter: new Date('2026-01-03T00:00:00Z'),
|
||||
},
|
||||
);
|
||||
|
||||
expect(first.threads.map(({ id }) => id)).toEqual([newest.id]);
|
||||
expect(second.threads.map(({ id }) => id)).toEqual([middle.id]);
|
||||
expect(third.threads.map(({ id }) => id)).toEqual([oldest.id]);
|
||||
expect(third.nextCursor).toBeNull();
|
||||
expect(after.threads.map(({ id }) => id)).toEqual([newest.id, middle.id, oldest.id]);
|
||||
});
|
||||
|
||||
it('filters private sessions before pagination and preserves their owner on reuse', async () => {
|
||||
const owner = await createMember();
|
||||
const other = await createAdmin();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user