Skip to content

Commit

Permalink
GraphQL: Add filtering on metadata to Samples and Attachments (#768)
Browse files Browse the repository at this point in the history
* feat: add in jcont and jcont_key ransackers for jsonb columns, update sample and attachments graphql filters to include metadata_jcont and metadat_jcont_key filter

* chore: introduce BaseRansackFilterInputObject which all FilterInputs are based off of

* chore: add in tests for new metadata filters

* chore: exclude jcont_all, jcont_any, jcont_key_all, jcont_key_any
  • Loading branch information
ericenns authored Sep 19, 2024
1 parent b92a2b4 commit c02efb0
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 14 deletions.
20 changes: 20 additions & 0 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,16 @@ input AttachmentFilter {
filename_start_all: [String!]
filename_start_any: [String!]
filename_true: String

"""
Filter attachments by metadata which contains the supplied key value pairs
"""
metadata_jcont: JSON

"""
Filter attachments by metadata which contains the key
"""
metadata_jcont_key: String
puid_blank: String
puid_cont: String
puid_cont_all: [String!]
Expand Down Expand Up @@ -2275,6 +2285,16 @@ input SampleFilter {
created_at_start_all: [String!]
created_at_start_any: [String!]
created_at_true: String

"""
Filter samples by metadata which contains the supplied key value pairs
"""
metadata_jcont: JSON

"""
Filter samples by metadata which contains the key
"""
metadata_jcont_key: String
name_blank: String
name_cont: String
name_cont_all: [String!]
Expand Down
16 changes: 13 additions & 3 deletions app/graphql/types/attachment_filter_input_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

module Types
# Attachment Filter Input Type
class AttachmentFilterInputType < BaseInputObject # rubocop:disable GraphQL/ObjectDescription
class AttachmentFilterInputType < BaseRansackFilterInputObject # rubocop:disable GraphQL/ObjectDescription
graphql_name 'AttachmentFilter'
Attachment.ransackable_attributes.each do |attr|
Ransack.predicates.keys.map do |key|
Attachment.ransackable_attributes.excluding(DEFAULT_EXCLUDED_ATTRIBUTES).each do |attr|
default_predicate_keys.map do |key|
value_type = Ransack.predicates[key].wants_array ? [String] : String
argument :"#{attr}_#{key}".to_sym,
value_type,
required: false,
camelize: false
end
end

argument :metadata_jcont, GraphQL::Types::JSON,
required: false, camelize: false,
prepare: lambda { |json, _ctx|
JSON.generate(json)
},
description: 'Filter attachments by metadata which contains the supplied key value pairs'
argument :metadata_jcont_key, String,
required: false, camelize: false,
description: 'Filter attachments by metadata which contains the key'
end
end
13 changes: 13 additions & 0 deletions app/graphql/types/base_ransack_filter_input_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Types
# Bsse Ransack Filter Input Object
class BaseRansackFilterInputObject < BaseInputObject
DEFAULT_EXCLUDED_ATTRIBUTES = %w[id metadata deleted_at].freeze
JSONB_PREDICATE_KEYS = %w[jcont jcont_all jcont_any jcont_key jcont_key_all jcont_key_any].freeze

def self.default_predicate_keys
Ransack.predicates.keys.excluding(JSONB_PREDICATE_KEYS)
end
end
end
6 changes: 3 additions & 3 deletions app/graphql/types/group_filter_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

module Types
# Group Filter Type
class GroupFilterType < BaseInputObject # rubocop:disable GraphQL/ObjectDescription
Group.ransackable_attributes.excluding(%w[id deleted_at]).each do |attr|
Ransack.predicates.keys.map do |key|
class GroupFilterType < BaseRansackFilterInputObject # rubocop:disable GraphQL/ObjectDescription
Group.ransackable_attributes.excluding(DEFAULT_EXCLUDED_ATTRIBUTES).each do |attr|
default_predicate_keys.map do |key|
value_type = Ransack.predicates[key].wants_array ? [String] : String
argument :"#{attr}_#{key}".to_sym,
value_type,
Expand Down
6 changes: 3 additions & 3 deletions app/graphql/types/project_filter_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

module Types
# Project Filter Type
class ProjectFilterType < BaseInputObject # rubocop:disable GraphQL/ObjectDescription
Project.ransackable_attributes.excluding('id').each do |attr|
Ransack.predicates.keys.map do |key|
class ProjectFilterType < BaseRansackFilterInputObject # rubocop:disable GraphQL/ObjectDescription
Project.ransackable_attributes.excluding(DEFAULT_EXCLUDED_ATTRIBUTES).each do |attr|
default_predicate_keys.map do |key|
value_type = Ransack.predicates[key].wants_array ? [String] : String
argument :"#{attr}_#{key}".to_sym,
value_type,
Expand Down
16 changes: 13 additions & 3 deletions app/graphql/types/sample_filter_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@

module Types
# Sample Filter Type
class SampleFilterType < BaseInputObject # rubocop:disable GraphQL/ObjectDescription
Sample.ransackable_attributes.excluding('id').each do |attr|
Ransack.predicates.keys.map do |key|
class SampleFilterType < BaseRansackFilterInputObject # rubocop:disable GraphQL/ObjectDescription
Sample.ransackable_attributes.excluding(DEFAULT_EXCLUDED_ATTRIBUTES).each do |attr|
default_predicate_keys.map do |key|
value_type = Ransack.predicates[key].wants_array ? [String] : String
argument :"#{attr}_#{key}".to_sym,
value_type,
required: false,
camelize: false
end
end

argument :metadata_jcont, GraphQL::Types::JSON,
required: false, camelize: false,
prepare: lambda { |json, _ctx|
JSON.generate(json)
},
description: 'Filter samples by metadata which contains the supplied key value pairs'
argument :metadata_jcont_key, String,
required: false, camelize: false,
description: 'Filter samples by metadata which contains the key'
end
end
2 changes: 1 addition & 1 deletion app/models/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def self.model_prefix
end

def self.ransackable_attributes(_auth_object = nil)
%w[puid created_at updated_at] + _ransack_aliases.keys
%w[puid metadata created_at updated_at] + _ransack_aliases.keys
end

def self.ransackable_associations(_auth_object = nil)
Expand Down
2 changes: 1 addition & 1 deletion app/models/sample.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def self.model_prefix
end

def self.ransackable_attributes(_auth_object = nil)
%w[id puid name created_at updated_at attachments_updated_at]
%w[id puid name metadata created_at updated_at attachments_updated_at]
end

def self.ransackable_associations(_auth_object = nil)
Expand Down
14 changes: 14 additions & 0 deletions config/initializers/ransack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Arel
module Predications # rubocop:disable Style/Documentation
def contains_key(other)
Arel::Nodes::InfixOperation.new(:'?', self, Arel::Nodes::Quoted.new(other))
end
end
end

Ransack.configure do |config|
config.add_predicate 'jcont', arel_predicate: 'contains', formatter: proc { |v| JSON.parse(v.to_s) }
config.add_predicate 'jcont_key', arel_predicate: 'contains_key'
end
67 changes: 67 additions & 0 deletions test/graphql/attachments_query_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,71 @@ def setup
assert_equal attachment1['id'], "gid://irida/Attachment/#{metadata2['associated_attachment_id']}"
assert_equal attachment2['id'], "gid://irida/Attachment/#{metadata1['associated_attachment_id']}"
end

test 'attachment query should work with metadata_jcont filter' do
result = IridaSchema.execute(
ATTACHMENTS_QUERY,
context: { current_user: @user_paired },
variables: { attachmentFilter: { metadata_jcont: { type: 'pe' } }, puid: @sample_paired.puid }
)

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

data = result['data']['sample']['attachments']['nodes']

assert_equal 6, data.count

attachment1 = data[0]
attachment2 = data[1]
metadata1 = attachment1['metadata']
metadata2 = attachment2['metadata']

assert_equal 'forward', metadata1['direction']
assert_equal 'reverse', metadata2['direction']

assert_equal 'pe', metadata1['type']
assert_equal 'pe', metadata2['type']

# check they reference each other
assert_equal attachment1['id'], "gid://irida/Attachment/#{metadata2['associated_attachment_id']}"
assert_equal attachment2['id'], "gid://irida/Attachment/#{metadata1['associated_attachment_id']}"
end

test 'attachment query should work with metadata_jcont filter to select only forward files' do
result = IridaSchema.execute(
ATTACHMENTS_QUERY,
context: { current_user: @user_paired },
variables: { attachmentFilter: { metadata_jcont: { type: 'pe', direction: 'forward' } },
puid: @sample_paired.puid }
)

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

data = result['data']['sample']['attachments']['nodes']
assert_equal 3, data.count

attachment1 = data[0]
metadata1 = attachment1['metadata']

assert_equal 'forward', metadata1['direction']

assert_equal 'pe', metadata1['type']
end

test 'attachment query should work with metadata_jcont_key filter' do
result = IridaSchema.execute(
ATTACHMENTS_QUERY,
context: { current_user: @user_paired },
variables: { attachmentFilter: { metadata_jcont_key: 'thisone' },
puid: @sample_paired.puid }
)

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

attachments = result['data']['sample']['attachments']['nodes']

puts attachments

assert_equal 0, attachments.count
end
end
36 changes: 36 additions & 0 deletions test/graphql/samples_query_ransack_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,40 @@ def setup
assert_nil data
end
end

test 'ransack samples query with metadata_jcont_key filter should work' do
result = IridaSchema.execute(SAMPLES_RANSACK_QUERY,
context: { current_user: @user },
variables: { filter: { metadata_jcont_key: 'metadatafield1' } })

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

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

assert_equal 4, data.count
end

test 'ransack samples query with metadata_jcont filter should work' do
result = IridaSchema.execute(SAMPLES_RANSACK_QUERY,
context: { current_user: @user },
variables: { filter: { metadata_jcont: { metadatafield1: 'value1' } } })

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

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

assert_equal 4, data.count
end

test 'ransack samples query with metadata_jcont_key filter should work with non_existent key' do
result = IridaSchema.execute(SAMPLES_RANSACK_QUERY,
context: { current_user: @user },
variables: { filter: { metadata_jcont_key: 'non_existent' } })

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

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

assert_equal 0, data.count
end
end

0 comments on commit c02efb0

Please sign in to comment.