diff --git a/server/app/graphql/resolvers/interactions.rb b/server/app/graphql/resolvers/interactions.rb new file mode 100644 index 00000000..1673209f --- /dev/null +++ b/server/app/graphql/resolvers/interactions.rb @@ -0,0 +1,62 @@ +require 'search_object' +require 'search_object/plugin/graphql' + +class Resolvers::Interactions < GraphQL::Schema::Resolver + include SearchObject.module(:graphql) + + type Types::InteractionType.connection_type, null: true + + scope { Interaction.all } + + option(:drug_names, type: [String], description: 'Filters interactions to only include those involving any of the specified drug names. This field accepts a list of drug names, which are case-insensitive, and returns interactions where the associated drug\'s name matches any name in the provided list.') do |scope, value| + if drug_names.nil? || drug_names.length == 0 + scope + else + names = value.map { |v| v.upcase } + scope.joins(:drug).where(drugs: {name: names}) + end + end + + option(:gene_names, type: [String], description: 'Filters interactions to only include those involving any of the specified gene names. This field accepts a list of gene names, which are case-insensitive, and returns interactions where the gene\'s name matches any name in the provided list.') do |scope, value| + if gene_names.nil? || gene_names.length == 0 + scope + else + names = value.map { |v| v.upcase } + scope.joins(:gene).where(genes: {name: names}) + end + end + + option(:drug_concept_ids, type: [String], description: 'Filters interactions to only include those involving any of the specified drug concept IDs. This field accepts a list of drug concept IDs, which are case-insensitive, and returns interactions where the associated drug\'s concept ID matches any ID in the provided list.') do |scope, value| + if drug_concept_ids.nil? || drug_concept_ids.length == 0 + scope + else + concept_ids = value.map { |v| v.upcase } + scope.joins(:drug).where(drugs: {concept_id: concept_ids}) + end + end + + option(:gene_concept_ids, type: [String], description: 'Filters interactions to only include those involving any of the specified gene concept IDs. This field accepts a list of gene concept IDs, which are case-insensitive, and returns interactions where the associated gene\'s concept ID matches any ID in the provided list.') do |scope, value| + if gene_concept_ids.nil? || gene_concept_ids.length == 0 + scope + else + concept_ids = value.map { |v| v.upcase } + scope.joins(:gene).where(genes: {concept_id: concept_ids}) + end + end + + option(:approved, type: Boolean, description: 'Filters interactions to include only those involving drugs with the specified approval status. Setting this to `true` returns interactions associated with approved drugs, while `false` returns interactions with drugs that are not known to be approved') do |scope, value| + scope.joins(:drug).where(drugs: {approved: value}) + end + + option(:immunotherapy, type: Boolean, description: 'Filters interactions based on whether the involved drugs are classified as immunotherapies. Set this to `true` to retrieve interactions involving immunotherapy drugs, or `false` to retrieve interactions involving non-immunotherapeutics') do |scope, value| + scope.joins(:drug).where(drugs: {immunotherapy: value}) + end + + option(:anti_neoplastic, type: Boolean, description: 'Filters interactions based on whether the involved drugs are classified as antineoplastics. Use `true` to include interactions involving antineoplastic drugs, or `false` to include those involving non-antineoplastic drugs') do |scope, value| + scope.joins(:drug).where(drugs: {anti_neoplastic: value}) + end + + option(:sources, type: [String], description: 'Filters interactions to include only those provided by the specified sources. This filter accepts a list of source names and returns interactions that have at least one matching source in the list') do |scope, value| + scope.joins(:sources).where(sources: {source_db_name: value}) + end +end diff --git a/server/app/graphql/types/query_type.rb b/server/app/graphql/types/query_type.rb index b04b5eae..3365c925 100644 --- a/server/app/graphql/types/query_type.rb +++ b/server/app/graphql/types/query_type.rb @@ -12,6 +12,7 @@ class QueryType < Types::BaseObject field :sources, resolver: Resolvers::Sources field :categories, resolver: Resolvers::Categories field :interaction_claim_types, resolver: Resolvers::InteractionClaimTypes + field :interactions, resolver: Resolvers::Interactions field :service_info, Types::MetaType, null: false @@ -317,7 +318,7 @@ def interaction_claim(id:) end field :interaction, Types::InteractionType, null: true do - description "An interaction" + description "An interaction between a drug and a gene" argument :id, ID, required: true end diff --git a/server/spec/factories/interaction.rb b/server/spec/factories/interaction.rb index 424f01d9..88910bc6 100644 --- a/server/spec/factories/interaction.rb +++ b/server/spec/factories/interaction.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :interaction do - sequence(:id) { |i| "DRUG #{i}" } # should always be uppercase + sequence(:id) { |i| "INTERACTION #{i}" } # should always be uppercase gene drug score { rand } diff --git a/server/spec/queries/interactions_queries_spec.rb b/server/spec/queries/interactions_queries_spec.rb new file mode 100644 index 00000000..338914b3 --- /dev/null +++ b/server/spec/queries/interactions_queries_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +RSpec.describe 'Interactions queries', type: :graphql do + before(:example) do + @drug1 = create(:drug) + @drug2 = create(:drug) + @drug3 = create(:drug) + + @gene1 = create(:gene) + @gene2 = create(:gene) + + @int1 = create(:interaction, drug: @drug1, gene: @gene1) + @int2 = create(:interaction, drug: @drug1, gene: @gene2) + @int3 = create(:interaction, drug: @drug2, gene: @gene1) + @int4 = create(:interaction, drug: @drug3, gene: @gene1) + end + + let :interactions_name_query do + <<-GRAPHQL + query interactions($drugNames: [String!], $geneNames: [String!]) { + interactions(drugNames: $drugNames, geneNames: $geneNames) { + edges { + node { + id + } + } + } + } + GRAPHQL + end + + it 'should execute basic interactions searches by name correctly' do + result = execute_graphql(interactions_name_query, variables: { drugNames: [@drug1.name] }) + expect(result["data"]["interactions"]["edges"].length).to eq 2 + + result = execute_graphql(interactions_name_query, variables: { drugNames: [@drug1.name], geneNames: [@gene1.name]}) + expect(result["data"]["interactions"]["edges"].length).to eq 1 + + result = execute_graphql(interactions_name_query, variables: { drugNames: [@drug1.name] , geneNames: []}) + expect(result["data"]["interactions"]["edges"].length).to eq 2 + + result = execute_graphql(interactions_name_query, variables: { drugNames: [], geneNames: [@gene1.name] }) + expect(result["data"]["interactions"]["edges"].length).to eq 3 + + result = execute_graphql(interactions_name_query, variables: { drugNames: [], geneNames: [@gene2.name] }) + expect(result["data"]["interactions"]["edges"].length).to eq 1 + + result = execute_graphql(interactions_name_query, variables: { drugNames: [@drug1.name, @drug2.name, @drug3.name], geneNames: [@gene1.name, @gene2.name] }) + expect(result["data"]["interactions"]["edges"].length).to eq 4 + end +end