Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0f93d8e
Initial pagination implementation starting with tabular reviews
amay0048 Jul 24, 2026
dabe4ab
Seed data for testing pagination
amay0048 Jul 25, 2026
aeff9fc
Pagination for tabular reviews smoke testing
amay0048 Jul 25, 2026
342702c
Display correctly for filtered results
amay0048 Jul 25, 2026
cc7e399
Wired the search into the pagination functionality
amay0048 Jul 25, 2026
f84c256
Add specific grants to fix db when restarting supabase
amay0048 Jul 25, 2026
7aa2359
Search and pagination is working
amay0048 Jul 26, 2026
d0848d6
Fix the keyword search, so that you can sort keyword paged results
amay0048 Jul 26, 2026
b854e25
Fix pagination bug with last call when the results are exactly a page
amay0048 Jul 26, 2026
f08b62c
Auto-call the load more on scroll
amay0048 Jul 26, 2026
614f0f0
Move the rpc changes into a new migration file
amay0048 Jul 26, 2026
937d0ee
Fix perf issues for the pagination
amay0048 Jul 26, 2026
034cd6e
Fix perf issues for the pagination
amay0048 Jul 26, 2026
e76fdae
Merge branch 'db-pagination' of github.com:amay0048/mike-amay0048 int…
amay0048 Jul 26, 2026
ec325c9
Restore the package-lock to main version
amay0048 Jul 26, 2026
55cc0a0
Prevent the search box from losing focus when results are returned
amay0048 Jul 26, 2026
6b0bd1d
fix: make tabular review pagination reliable
willchen96 Jul 26, 2026
bf45f94
Support select all then delete functionality with pagination
amay0048 Jul 27, 2026
25a082f
Support select all then delete functionality with pagination
amay0048 Jul 27, 2026
ae6b797
Merge branch 'db-pagination' of github.com:amay0048/mike-amay0048 int…
amay0048 Jul 27, 2026
d6b35c9
fix: harden paginated review selection and deletion
willchen96 Jul 30, 2026
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
216 changes: 216 additions & 0 deletions backend/migrations/20260726_01_tabular_reviews_pagination.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
-- Migration date: 2026-07-26

-- Performance pass on get_tabular_reviews_overview:
-- 1. Compute document_count once (previously recomputed in the select list and
-- twice more in the `documents` sort case branches) and reuse the value.
-- 2. Narrow cell_document_counts to only the reviews that will actually use it
-- (document_ids is not a jsonb array), instead of aggregating tabular_cells
-- for every visible review regardless of whether the result gets used.
-- 3. Add a trigram index so the leading-wildcard title search can use an index
-- instead of a full scan of the pre-filtered set.

create extension if not exists pg_trgm;

create index if not exists tabular_reviews_title_trgm_idx
on public.tabular_reviews using gin (lower(title) gin_trgm_ops);

-- Remove the earlier paginated signature if this migration was tested before
-- scope filtering was added. The legacy three-argument signature is retained
-- below as a non-ambiguous compatibility wrapper.
drop function if exists public.get_tabular_reviews_overview(
text, text, text, integer, integer, text, text, text
);

create or replace function public.get_tabular_reviews_overview(
p_user_id text,
p_user_email text,
p_project_id text,
p_scope text,
p_limit integer,
p_offset integer,
p_search_term text,
p_sort_key text,
p_sort_direction text
)
returns table (
id uuid,
project_id uuid,
user_id text,
title text,
columns_config jsonb,
document_ids jsonb,
workflow_id uuid,
shared_with jsonb,
created_at timestamptz,
updated_at timestamptz,
is_owner boolean,
document_count integer
)
language sql
stable
as $$
with accessible_projects as (
select p.id
from public.projects p
where p.user_id = p_user_id
or (
coalesce(p_user_email, '') <> ''
and p.user_id <> p_user_id
and p.shared_with @> jsonb_build_array(p_user_email)
)
),
visible_reviews as (
select tr.*
from public.tabular_reviews tr
where (p_project_id is null or tr.project_id::text = p_project_id)
and (
coalesce(p_scope, 'all') = 'all'
or (p_scope = 'in-project' and tr.project_id is not null)
or (p_scope = 'standalone' and tr.project_id is null)
)
and (
p_search_term is null
or p_search_term = ''
or lower(tr.title) like '%' || lower(p_search_term) || '%'
)
and (
p_project_id is null
or exists (
select 1
from accessible_projects ap
where ap.id::text = p_project_id
)
)
and (
tr.user_id = p_user_id
or (
tr.project_id in (select ap.id from accessible_projects ap)
and tr.user_id <> p_user_id
)
or (
p_project_id is null
and coalesce(p_user_email, '') <> ''
and tr.user_id <> p_user_id
and tr.shared_with @> jsonb_build_array(p_user_email)
)
)
),
cell_document_counts as (
select
tc.review_id,
count(distinct tc.document_id)::integer as document_count
from public.tabular_cells tc
where tc.review_id in (
select vr.id
from visible_reviews vr
where jsonb_typeof(vr.document_ids) is distinct from 'array'
)
group by tc.review_id
),
review_document_counts as (
select
vr.id,
case
when jsonb_typeof(vr.document_ids) = 'array'
then (
select count(distinct doc_id.value)::integer
from jsonb_array_elements_text(vr.document_ids) as doc_id(value)
)
else coalesce(cdc.document_count, 0)
end as document_count
from visible_reviews vr
left join cell_document_counts cdc
on cdc.review_id = vr.id
)
select
vr.id,
vr.project_id,
vr.user_id,
vr.title,
vr.columns_config,
vr.document_ids,
vr.workflow_id,
vr.shared_with,
vr.created_at,
vr.updated_at,
vr.user_id = p_user_id as is_owner,
rdc.document_count
from visible_reviews vr
join review_document_counts rdc
on rdc.id = vr.id
order by
case
when p_sort_key = 'name' and p_sort_direction = 'asc' then lower(coalesce(vr.title, ''))
else null
end asc,
case
when p_sort_key = 'name' and p_sort_direction = 'desc' then lower(coalesce(vr.title, ''))
else null
end desc,
case
when p_sort_key = 'columns' and p_sort_direction = 'asc' then jsonb_array_length(coalesce(vr.columns_config, '[]'::jsonb))
else null
end asc,
case
when p_sort_key = 'columns' and p_sort_direction = 'desc' then jsonb_array_length(coalesce(vr.columns_config, '[]'::jsonb))
else null
end desc,
case
when p_sort_key = 'documents' and p_sort_direction = 'asc' then rdc.document_count
else null
end asc,
case
when p_sort_key = 'documents' and p_sort_direction = 'desc' then rdc.document_count
else null
end desc,
case
when p_sort_key = 'created' and p_sort_direction = 'asc' then vr.created_at
else null
end asc,
case
when p_sort_key = 'created' and p_sort_direction = 'desc' then vr.created_at
else null
end desc,
vr.created_at desc,
vr.id asc
limit greatest(coalesce(p_limit, 20), 1)
offset greatest(coalesce(p_offset, 0), 0);
$$;

-- Preserve the pre-pagination RPC contract without making PostgREST choose
-- between two functions that can both accept the same three arguments.
create or replace function public.get_tabular_reviews_overview(
p_user_id text,
p_user_email text default null,
p_project_id text default null
)
returns table (
id uuid,
project_id uuid,
user_id text,
title text,
columns_config jsonb,
document_ids jsonb,
workflow_id uuid,
shared_with jsonb,
created_at timestamptz,
updated_at timestamptz,
is_owner boolean,
document_count integer
)
language sql
stable
as $$
select *
from public.get_tabular_reviews_overview(
p_user_id,
p_user_email,
p_project_id,
'all',
2147483647,
0,
null,
'created',
'desc'
);
$$;
136 changes: 122 additions & 14 deletions backend/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-- newer than the version of Mike they currently have deployed.

create extension if not exists "pgcrypto";
create extension if not exists "pg_trgm";

-- ---------------------------------------------------------------------------
-- User profiles
Expand Down Expand Up @@ -602,6 +603,9 @@ create index if not exists idx_tabular_reviews_project
create index if not exists tabular_reviews_shared_with_idx
on public.tabular_reviews using gin (shared_with);

create index if not exists tabular_reviews_title_trgm_idx
on public.tabular_reviews using gin (lower(title) gin_trgm_ops);

create or replace function public.get_projects_overview(
p_user_id text,
p_user_email text default null
Expand Down Expand Up @@ -694,10 +698,20 @@ create table if not exists public.tabular_cells (
create index if not exists idx_tabular_cells_review
on public.tabular_cells(review_id, document_id, column_index);

drop function if exists public.get_tabular_reviews_overview(
text, text, text, integer, integer, text, text, text
);

create or replace function public.get_tabular_reviews_overview(
p_user_id text,
p_user_email text default null,
p_project_id text default null
p_user_email text,
p_project_id text,
p_scope text,
p_limit integer,
p_offset integer,
p_search_term text,
p_sort_key text,
p_sort_direction text
)
returns table (
id uuid,
Expand Down Expand Up @@ -730,6 +744,16 @@ as $$
select tr.*
from public.tabular_reviews tr
where (p_project_id is null or tr.project_id::text = p_project_id)
and (
coalesce(p_scope, 'all') = 'all'
or (p_scope = 'in-project' and tr.project_id is not null)
or (p_scope = 'standalone' and tr.project_id is null)
)
and (
p_search_term is null
or p_search_term = ''
or lower(tr.title) like '%' || lower(p_search_term) || '%'
)
and (
p_project_id is null
or exists (
Expand Down Expand Up @@ -757,8 +781,27 @@ as $$
tc.review_id,
count(distinct tc.document_id)::integer as document_count
from public.tabular_cells tc
where tc.review_id in (select vr.id from visible_reviews vr)
where tc.review_id in (
select vr.id
from visible_reviews vr
where jsonb_typeof(vr.document_ids) is distinct from 'array'
)
group by tc.review_id
),
review_document_counts as (
select
vr.id,
case
when jsonb_typeof(vr.document_ids) = 'array'
then (
select count(distinct doc_id.value)::integer
from jsonb_array_elements_text(vr.document_ids) as doc_id(value)
)
else coalesce(cdc.document_count, 0)
end as document_count
from visible_reviews vr
left join cell_document_counts cdc
on cdc.review_id = vr.id
)
select
vr.id,
Expand All @@ -772,18 +815,83 @@ as $$
vr.created_at,
vr.updated_at,
vr.user_id = p_user_id as is_owner,
case
when jsonb_typeof(vr.document_ids) = 'array'
then (
select count(distinct doc_id.value)::integer
from jsonb_array_elements_text(vr.document_ids) as doc_id(value)
)
else coalesce(cdc.document_count, 0)
end as document_count
rdc.document_count
from visible_reviews vr
left join cell_document_counts cdc
on cdc.review_id = vr.id
order by vr.created_at desc;
join review_document_counts rdc
on rdc.id = vr.id
order by
case
when p_sort_key = 'name' and p_sort_direction = 'asc' then lower(coalesce(vr.title, ''))
else null
end asc,
case
when p_sort_key = 'name' and p_sort_direction = 'desc' then lower(coalesce(vr.title, ''))
else null
end desc,
case
when p_sort_key = 'columns' and p_sort_direction = 'asc' then jsonb_array_length(coalesce(vr.columns_config, '[]'::jsonb))
else null
end asc,
case
when p_sort_key = 'columns' and p_sort_direction = 'desc' then jsonb_array_length(coalesce(vr.columns_config, '[]'::jsonb))
else null
end desc,
case
when p_sort_key = 'documents' and p_sort_direction = 'asc' then rdc.document_count
else null
end asc,
case
when p_sort_key = 'documents' and p_sort_direction = 'desc' then rdc.document_count
else null
end desc,
case
when p_sort_key = 'created' and p_sort_direction = 'asc' then vr.created_at
else null
end asc,
case
when p_sort_key = 'created' and p_sort_direction = 'desc' then vr.created_at
else null
end desc,
vr.created_at desc,
vr.id asc
limit greatest(coalesce(p_limit, 20), 1)
offset greatest(coalesce(p_offset, 0), 0);
$$;

create or replace function public.get_tabular_reviews_overview(
p_user_id text,
p_user_email text default null,
p_project_id text default null
)
returns table (
id uuid,
project_id uuid,
user_id text,
title text,
columns_config jsonb,
document_ids jsonb,
workflow_id uuid,
shared_with jsonb,
created_at timestamptz,
updated_at timestamptz,
is_owner boolean,
document_count integer
)
language sql
stable
as $$
select *
from public.get_tabular_reviews_overview(
p_user_id,
p_user_email,
p_project_id,
'all',
2147483647,
0,
null,
'created',
'desc'
);
$$;

create table if not exists public.tabular_review_chats (
Expand Down
Loading