Skip to content

Commit

Permalink
GraphQL: Samples filtering and ordering (#743)
Browse files Browse the repository at this point in the history
* feat: add in filtering and ordering to project samples resolver and add ordering to samples resolver

* chore: add in tests for filtering and ordering project samples, and fixed scope_items to preserve ordering

* chore: add tests for samples ordering

* chore: fix rubocop violations

* chore: fix rubocop warnings

* chore: fix rubocop warnings

* chore: fixed last remaining rubocop warning
  • Loading branch information
ericenns authored Sep 9, 2024
1 parent 21f35ff commit c01473f
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 19 deletions.
18 changes: 15 additions & 3 deletions app/graphql/resolvers/project_samples_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@ module Resolvers
class ProjectSamplesResolver < BaseResolver
alias project object

def resolve
scope = project
scope.samples
argument :filter, Types::SampleFilterType,
required: false,
description: 'Ransack filter',
default_value: nil

argument :order_by, Types::SampleOrderInputType,
required: false,
description: 'Order by',
default_value: nil

def resolve(filter:, order_by:)
ransack_obj = project.samples.ransack(filter&.to_h)
ransack_obj.sorts = ["#{order_by.field} #{order_by.direction}"] if order_by.present?

ransack_obj.result
end
end
end
12 changes: 10 additions & 2 deletions app/graphql/resolvers/samples_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ class SamplesResolver < BaseResolver
description: 'Ransack filter',
default_value: nil

def resolve(group_id:, filter:)
argument :order_by, Types::SampleOrderInputType,
required: false,
description: 'Order by',
default_value: nil

def resolve(group_id:, filter:, order_by:)
samples = group_id ? samples_by_group_scope(group_id:) : samples_by_project_scope
filter ? samples.ransack(filter.to_h).result : samples
ransack_obj = samples.ransack(filter&.to_h)
ransack_obj.sorts = ["#{order_by.field} #{order_by.direction}"] if order_by.present?

ransack_obj.result
end

def ready?(**_args)
Expand Down
31 changes: 31 additions & 0 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,11 @@ type Project implements Node {
"""
before: String

"""
Ransack filter
"""
filter: SampleFilter = null

"""
Returns the first _n_ elements from the list.
"""
Expand All @@ -932,6 +937,11 @@ type Project implements Node {
Returns the last _n_ elements from the list.
"""
last: Int

"""
Order by
"""
orderBy: SampleOrder = null
): SampleConnection

"""
Expand Down Expand Up @@ -1183,6 +1193,11 @@ type Query {
Returns the last _n_ elements from the list.
"""
last: Int

"""
Order by
"""
orderBy: SampleOrder = null
): SampleConnection
}

Expand Down Expand Up @@ -1629,6 +1644,22 @@ input SampleFilter {
updated_at_true: String
}

"""
Specify a sort for the samples
"""
input SampleOrder {
direction: OrderDirection
field: SampleOrderField!
}

"""
Field to sort the samples by
"""
enum SampleOrderField {
created_at
updated_at
}

"""
Autogenerated input type of UpdateSampleMetadata
"""
Expand Down
3 changes: 2 additions & 1 deletion app/graphql/types/project_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def self.authorized?(object, context)
def self.scope_items(items, context)
scope = authorized_scope Project, type: :relation,
context: { user: context[:current_user], token: context[:token] }
scope.where(id: items.select(:id))
project_ids = items.pluck(:id)
scope.where(id: project_ids).in_order_of(:id, project_ids)
end

reauthorize_scoped_objects(false)
Expand Down
18 changes: 18 additions & 0 deletions app/graphql/types/sample_order_input_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Types
class SampleOrderFieldInputType < BaseEnum # rubocop:disable Style/Documentation
graphql_name 'SampleOrderField'
description 'Field to sort the samples by'
value 'created_at'
value 'updated_at'
end

class SampleOrderInputType < BaseInputObject # rubocop:disable Style/Documentation
graphql_name 'SampleOrder'
description 'Specify a sort for the samples'

argument :direction, OrderDirectionType, required: false # rubocop:disable GraphQL/ArgumentDescription
argument :field, SampleOrderFieldInputType, required: true # rubocop:disable GraphQL/ArgumentDescription
end
end
3 changes: 2 additions & 1 deletion app/graphql/types/sample_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def self.authorized?(object, context)
def self.scope_items(items, context)
scope = authorized_scope Project, type: :relation,
context: { user: context[:current_user], token: context[:token] }
Sample.where(id: items.select(:id), project_id: scope.select(:id))
sample_ids = items.pluck(:id)
Sample.where(id: sample_ids, project_id: scope.select(:id)).in_order_of(:id, sample_ids)
end

reauthorize_scoped_objects(false)
Expand Down
66 changes: 58 additions & 8 deletions test/graphql/project_samples_query_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

class ProjectSamplesQueryTest < ActiveSupport::TestCase
PROJECT_QUERY = <<~GRAPHQL
query($projectPath: ID!) {
query($projectPath: ID!, $sampleFilter: SampleFilter, $sampleOrderBy: SampleOrder) {
project(fullPath: $projectPath) {
name
path
id
samples(first:10) {
samples(first:10, filter: $sampleFilter, orderBy: $sampleOrderBy) {
nodes {
id
name
Expand Down Expand Up @@ -44,9 +44,9 @@ def setup

# verify fetched sample data matches data on project
project.samples.each_with_index do |sample, index|
assert_equal data['samples']['nodes'][index]['id'], sample.to_global_id.to_s
assert_equal data['samples']['nodes'][index]['name'], sample.name
assert_equal data['samples']['nodes'][index]['project']['id'], project.to_global_id.to_s
assert_equal sample.name, data['samples']['nodes'][index]['name']
assert_equal sample.to_global_id.to_s, data['samples']['nodes'][index]['id']
assert_equal project.to_global_id.to_s, data['samples']['nodes'][index]['project']['id']
end
end

Expand All @@ -70,9 +70,9 @@ def setup

# verify fetched sample data matches data on project
project.samples.each_with_index do |sample, index|
assert_equal data['samples']['nodes'][index]['id'], sample.to_global_id.to_s
assert_equal data['samples']['nodes'][index]['name'], sample.name
assert_equal data['samples']['nodes'][index]['project']['id'], project.to_global_id.to_s
assert_equal sample.name, data['samples']['nodes'][index]['name']
assert_equal sample.to_global_id.to_s, data['samples']['nodes'][index]['id']
assert_equal project.to_global_id.to_s, data['samples']['nodes'][index]['project']['id']
end
end

Expand All @@ -92,4 +92,54 @@ def setup

assert_equal I18n.t('action_policy.policy.project.read?', name: project.name), error_message
end

test 'project with sample query should work with filter' do
project = projects(:project1)

result = IridaSchema.execute(PROJECT_QUERY, context: { current_user: @user },
variables: { projectPath: project.full_path,
sampleFilter: { name_cont: 'Sample 2' } })

assert_nil result['errors'], 'should work and have no errors.'

data = result['data']['project']

assert_not_empty data, 'project type should work'
assert_equal project.name, data['name']

assert_equal project.to_global_id.to_s, data['id'], 'id should be GlobalID'
assert_equal 2, data['samples']['nodes'].count

# verify fetched sample data only includes ones with `Sample 2` in the name
project.samples.where('name ILIKE \'%Sample 2%\'').each_with_index do |sample, index|
assert_equal sample.name, data['samples']['nodes'][index]['name']
assert_equal sample.to_global_id.to_s, data['samples']['nodes'][index]['id']
assert_equal project.to_global_id.to_s, data['samples']['nodes'][index]['project']['id']
end
end

test 'project with sample query should work with order by' do
project = projects(:project1)

result = IridaSchema.execute(PROJECT_QUERY, context: { current_user: @user },
variables: { projectPath: project.full_path,
sampleOrderBy: { field: 'created_at', direction: 'asc' } })

assert_nil result['errors'], 'should work and have no errors.'

data = result['data']['project']

assert_not_empty data, 'project type should work'
assert_equal project.name, data['name']

assert_equal project.to_global_id.to_s, data['id'], 'id should be GlobalID'
assert_equal project.samples.count, data['samples']['nodes'].count

# verify fetched sample data matches data on project
project.samples.order(created_at: :asc).each_with_index do |sample, index|
assert_equal sample.name, data['samples']['nodes'][index]['name']
assert_equal sample.to_global_id.to_s, data['samples']['nodes'][index]['id']
assert_equal project.to_global_id.to_s, data['samples']['nodes'][index]['project']['id']
end
end
end
41 changes: 37 additions & 4 deletions test/graphql/samples_query_ransack_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

class SamplesQueryRansackTest < ActiveSupport::TestCase
SAMPLES_RANSACK_QUERY = <<~GRAPHQL
query($filter: SampleFilter) {
samples(filter: $filter) {
query($filter: SampleFilter, $orderBy: SampleOrder) {
samples(filter: $filter, orderBy: $orderBy) {
nodes {
name
description
Expand All @@ -22,8 +22,8 @@ class SamplesQueryRansackTest < ActiveSupport::TestCase
GRAPHQL

SAMPLES_RANSACK_WITH_GROUP_QUERY = <<~GRAPHQL
query($filter: SampleFilter, $group_id: ID!) {
samples(filter: $filter, groupId: $group_id) {
query($filter: SampleFilter, $group_id: ID!, $orderBy: SampleOrder) {
samples(filter: $filter, groupId: $group_id, orderBy: $orderBy) {
nodes {
name
description
Expand Down Expand Up @@ -64,6 +64,20 @@ def setup
end
end

test 'samples query should work with order by' do
result = IridaSchema.execute(SAMPLES_RANSACK_QUERY,
context: { current_user: @user },
variables: { filter: { name_start: 'Project 1' },
orderBy: { field: 'created_at', direction: 'asc' } })

assert_nil result['errors'], 'should work and have no errors.'

data = result['data']['samples']['nodes']

assert_equal samples(:sample2).name, data[0]['name']
assert_equal samples(:sample2).puid, data[0]['puid']
end

test 'ransack samples query with group id should work' do
original_date = Time.zone.today

Expand All @@ -86,6 +100,25 @@ def setup
end
end

test 'ransack samples query with group id should work with order by' do
Timecop.travel(5.days.from_now) do
@sample.created_at = Time.zone.now
@sample.save!

result = IridaSchema.execute(SAMPLES_RANSACK_WITH_GROUP_QUERY,
context: { current_user: @user },
variables:
{ group_id: groups(:group_one).to_global_id.to_s,
orderBy: { field: 'created_at', direction: 'desc' } })

assert_nil result['errors'], 'should work and have no errors.'

data = result['data']['samples']['nodes']

assert_equal @sample.puid, data[0]['puid']
end
end

test 'ransack group samples query should throw authorization error' do
original_date = Time.zone.today

Expand Down

0 comments on commit c01473f

Please sign in to comment.