Add GraphQL sort/filter/page crm contacts
What does this MR do and why?
In preparation for upgrading the CRM contacts UI to support pagination, sorting and filtering, this MR adds those functionalities to the backend and exposes them via GraphQL.
The UI component also displays counts in tabs for active, inactive and all contacts (hence we had to add the ContactStateCounts
too.
SQL Queries
Example main query:
SELECT "customer_relations_contacts".* FROM "customer_relations_contacts" WHERE "customer_relations_contacts"."group_id" = 8250287 AND "customer_relations_contacts"."state" = 1 ORDER BY "customer_relations_contacts"."first_name" DESC NULLS LAST, "customer_relations_contacts"."id" DESC LIMIT 100
Plan (take with a pinch of salt because the lack of test data (I ran for group )- I have asked if we can seed some data internal link
Limit (cost=4.63..4.64 rows=2 width=108) (actual time=3.588..3.591 rows=2 loops=1)
Buffers: shared hit=9 read=3
I/O Timings: read=3.418 write=0.000
-> Sort (cost=4.63..4.64 rows=2 width=108) (actual time=3.586..3.587 rows=2 loops=1)
Sort Key: customer_relations_contacts.first_name DESC NULLS LAST, customer_relations_contacts.id DESC
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=9 read=3
I/O Timings: read=3.418 write=0.000
-> Index Scan using index_customer_relations_contacts_on_group_id on public.customer_relations_contacts (cost=0.14..4.62 rows=2 width=108) (actual time=2.815..3.499 rows=2 loops=1)
Index Cond: (customer_relations_contacts.group_id = 8250287)
Filter: (customer_relations_contacts.state = 1)
Rows Removed by Filter: 0
Buffers: shared hit=3 read=3
I/O Timings: read=3.418 write=0.000
Time: 4.747 ms
- planning: 1.094 ms
- execution: 3.653 ms
- I/O read: 3.418 ms
- I/O write: 0.000 ms
Shared buffers:
- hits: 9 (~72.00 KiB) from the buffer pool
- reads: 3 (~24.00 KiB) from the OS file cache, including disk I/O
- dirtied: 0
- writes: 0
Details and visualization: https://postgres.ai/console/gitlab/gitlab-production-tunnel-pg12/sessions/11399/commands/40753
Example counts query:
SELECT COUNT(*) AS count_all, "customer_relations_contacts"."state" AS customer_relations_contacts_state FROM "customer_relations_contacts" WHERE "customer_relations_contacts"."group_id" = 8250287 AND ((("customer_relations_contacts"."first_name" ILIKE '%wa%' OR "customer_relations_contacts"."last_name" ILIKE '%wa%') OR "customer_relations_contacts"."email" ILIKE '%wa%') OR "customer_relations_contacts"."description" ILIKE '%wa%') GROUP BY "customer_relations_contacts"."state"
Plan
Aggregate (cost=4.65..4.67 rows=1 width=10) (actual time=0.055..0.056 rows=0 loops=1)
Group Key: customer_relations_contacts.state
Buffers: shared hit=9
I/O Timings: read=0.000 write=0.000
-> Sort (cost=4.65..4.65 rows=1 width=2) (actual time=0.054..0.054 rows=0 loops=1)
Sort Key: customer_relations_contacts.state
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=9
I/O Timings: read=0.000 write=0.000
-> Index Scan using index_customer_relations_contacts_on_group_id on public.customer_relations_contacts (cost=0.14..4.64 rows=1 width=2) (actual time=0.034..0.034 rows=0 loops=1)
Index Cond: (customer_relations_contacts.group_id = 8250287)
Filter: ((customer_relations_contacts.first_name ~~* '%wa%'::text) OR (customer_relations_contacts.last_name ~~* '%wa%'::text) OR (customer_relations_contacts.email ~~* '%wa%'::text) OR (customer_relations_contacts.description ~~* '%wa%'::text))
Rows Removed by Filter: 2
Buffers: shared hit=6
I/O Timings: read=0.000 write=0.000
Time: 1.192 ms
- planning: 1.043 ms
- execution: 0.149 ms
- I/O read: 0.000 ms
- I/O write: 0.000 ms
Shared buffers:
- hits: 9 (~72.00 KiB) from the buffer pool
- reads: 0 from the OS file cache, including disk I/O
- dirtied: 0
- writes: 0
How to set up and validate locally
You'll need a group with some contacts (ideally active and inactive). If you've recently reseeded your GDK you should already have them.
Otherwise, you should be able to do something like this from rails:
FactoryBot.create_list(:contact, 10, group: Group.find(31), state: 'inactive')
Then use graphql-explorer and play with a query something like:
query {
group(fullPath: "flightjs") {
__typename
id
contacts(
state: inactive
search: null
sort: LAST_NAME_ASC
) {
nodes {
id
firstName
lastName
email
phone
description
organization {
__typename
id
name
}
__typename
}
pageInfo {
hasNextPage
endCursor
hasPreviousPage
startCursor
__typename
}
__typename
}
}
}
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Related to #2256