diff --git a/app/graphql/types/event_queries.rb b/app/graphql/types/event_queries.rb new file mode 100644 index 000000000..3e596ea5d --- /dev/null +++ b/app/graphql/types/event_queries.rb @@ -0,0 +1,87 @@ +module Types + + module EventQueries + def self.included(klass) + klass.field :event, EventType, "Find Event by ID" do + argument :id, ID + end + + klass.field :event_connection, Types::EventType.connection_type + + klass.field :events_by_filter, [Types::EventType] do + argument :from_date, String, required: false + argument :to_date, String, required: false + argument :neighbourhood_id, Integer, required: false + argument :tag_id, Integer, required: false + end + end + + def event(id:) + Event.find(id) + end + + def event_connection(**args) + Event.sort_by_time.all + end + + def events_by_filter(**args) + query = Event.sort_by_time + from_date = DateTime.now.beginning_of_day + + if args[:from_date].present? + if args[:from_date] =~ /^\s*(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/ + from_date = DateTime.new( + $1.to_i, # year + $2.to_i, # month + $3.to_i, # day + $4.to_i, # hour + $5.to_i, # minute + 0, # seconds + ) + else + raise GraphQL::ExecutionError, "fromDate not in 'YYYY-MM-DD HH:MM' format" + end + end + + query = query.where('dtstart >= ?', from_date) + + if args[:to_date].present? + if args[:to_date] =~ /^\s*(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/ + to_date = DateTime.new( + $1.to_i, # year + $2.to_i, # month + $3.to_i, # day + $4.to_i, # hour + $5.to_i, # minute + 0, # seconds + ) + + if to_date <= from_date + raise GraphQL::ExecutionError, "toDate is before fromDate" + end + + else + raise GraphQL::ExecutionError, "toDate not in 'YYYY-MM-DD HH:MM' format" + end + + query = query.where('dtstart < ?', to_date) + end + + if args[:neighbourhood_id].present? + neighbourhood = Neighbourhood.where(id: args[:neighbourhood_id]).first + raise GraphQL::ExecutionError, "Could not find neighbourhood with that ID (#{args[:neighbourhood_id]})" if neighbourhood.nil? + + query = query.for_neighbourhoods(neighbourhood.subtree) + end + + if args[:tag_id].present? + tag = Tag.where(id: args[:tag_id]).first + raise GraphQL::ExecutionError, "Could not find tag with that ID (#{args[:tag_id]})" if tag.nil? + + query = query.for_tag(tag) + end + + query.all + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 8a1a41d27..d35aa10a2 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -19,24 +19,6 @@ def partner_connection(**args) end end - module EventQueries - def self.included(klass) - klass.field :event, EventType, "Find Event by ID" do - argument :id, ID - end - - klass.field :event_connection, Types::EventType.connection_type - end - - def event(id:) - Event.find(id) - end - - def event_connection(**args) - Event.sort_by_time.all - end - end - module SiteQueries def self.included(klass) klass.field :site, SiteType, "Find Site by ID" do diff --git a/app/models/event.rb b/app/models/event.rb index 62ede534c..158de49ef 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -30,6 +30,24 @@ class Event < ApplicationRecord where('DATE(dtstart) >= ? AND DATE(dtstart) <= ?', week_start, week_end) } + # For the API eventFilter find by neighbourhood + scope :for_neighbourhoods, lambda { |neighbourhoods| + neighbourhood_ids = neighbourhoods.map(&:id) + + joins('left outer join partners on events.partner_id = partners.id') + .joins('left outer join addresses on partners.address_id = addresses.id') + .joins('left outer join service_areas on partners.id = service_areas.partner_id') + .where('(service_areas.neighbourhood_id in (?)) or (addresses.neighbourhood_id in (?))', neighbourhood_ids, neighbourhood_ids) + } + + # For the API eventFilter find by tag + scope :for_tag, lambda { |tag| + joins(:partner) + .joins('left outer join partner_tags on partners.id = partner_tags.partner_id') + .where('partner_tags.tag_id = ?', tag.id) + } + + # Filter by Site scope :for_site, lambda { |site| site_neighbourhood_ids = site.owned_neighbourhoods.map(&:id) diff --git a/test/factories/address.rb b/test/factories/address.rb index 3910d9d18..6b97c2b1b 100644 --- a/test/factories/address.rb +++ b/test/factories/address.rb @@ -18,6 +18,26 @@ end end + factory :bare_address_1, class: 'Address' do + street_address { '123 Moss Ln E' } + street_address2 { 'Manchester' } + city { 'Manchester' } + country_code { 'UK' } + latitude { 53.4651064 } + longitude { -2.2484797 } + postcode { 'M15 5DD' } + end + + factory :bare_address_2, class: 'Address' do + street_address { '42 Alexandra Rd' } + street_address2 { 'Moss Side' } + city { 'Manchester' } + country_code { 'UK' } + latitude { '53.430720' } + longitude { '-2.436610' } + postcode { 'M16 7BA' } + end + factory :moss_side_address, class: 'Address' do street_address { '42 Alexandra Rd' } street_address2 { 'Moss Side' } diff --git a/test/integration/graphql/event_integration_test.rb b/test/integration/graphql/event_integration_test.rb index 80e49fa19..dd83954f0 100644 --- a/test/integration/graphql/event_integration_test.rb +++ b/test/integration/graphql/event_integration_test.rb @@ -5,7 +5,8 @@ class GraphQLEventTest < ActionDispatch::IntegrationTest setup do - @partner = FactoryBot.create(:partner) + partner_address = FactoryBot.create(:bare_address_1, neighbourhood: neighbourhoods(:one)) + @partner = FactoryBot.create(:partner, address: partner_address) @address = @partner.address assert @address, 'Failed to create Address from partner' @@ -45,6 +46,7 @@ class GraphQLEventTest < ActionDispatch::IntegrationTest GRAPHQL result = PlaceCalSchema.execute(query_string) + # puts JSON.pretty_generate(result.as_json) data = result['data'] assert data.has_key?('eventConnection'), 'result is missing key `eventConnection`' @@ -105,4 +107,273 @@ class GraphQLEventTest < ActionDispatch::IntegrationTest assert data_event.has_key?('address'), 'missing address' assert data_event.has_key?('organizer'), 'missing organizer' end + + # the filter tests + + def build_time_events(now_time) + + # events in the past + time = now_time - 100.days + 5.times do + @partner.events.create!( + dtstart: time, + summary: "past: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + time += 1.days + end + + # events in the near future + time = now_time + 1.days + 5.times do + @partner.events.create!( + dtstart: time, + summary: "present: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + time += 1.days + end + + # events in the far future + time = now_time + 100.days + 5.times do + @partner.events.create!( + dtstart: time, + summary: "future: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + time += 1.days + end + end + + test 'returns events from today' do + now_time = DateTime.new(1990, 1, 1, 0, 0, 0) + build_time_events now_time + + DateTime.stub :now, now_time do + + query_string = <<-GRAPHQL + query { + eventsByFilter { + id + name + startDate + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 10, 'was expecting only events in the future' + end + end + + test 'returns events from a given point in time' do + now_time = DateTime.new(1990, 1, 1, 0, 0, 0) + build_time_events now_time + + DateTime.stub :now, now_time do + + query_string = <<-GRAPHQL + query { + eventsByFilter(fromDate: "1985-01-01 00:00") { + id + name + startDate + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 15, 'was expecting to see all events' + end + end + + test 'returns events in a given range when blocked out' do + now_time = DateTime.new(1990, 1, 1, 0, 0, 0) + build_time_events now_time + + DateTime.stub :now, now_time do + + query_string = <<-GRAPHQL + query { + eventsByFilter(fromDate: "1990-01-01 00:00", toDate: "1990-03-01 00:00") { + id + name + startDate + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 5, 'was expecting to see only some future events' + end + end + + # this should mainly be tested elsewhere + # (the same place service area scoping is tested) + test 'can scope to neighbourhood (via partner address)' do + + 3.times do + @partner.events.create!( + dtstart: Date.today, + summary: "partner 1: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + end + + + other_address = FactoryBot.create(:bare_address_2, neighbourhood: neighbourhoods(:two)) + other_partner = FactoryBot.create(:moss_side_partner, address: other_address) + + 5.times do + other_partner.events.create!( + dtstart: Date.today, + summary: "partner 2: An event summary", + description: 'Longer text covering the event in more detail', + address: other_address + ) + end + + query_string = <<-GRAPHQL + query { + eventsByFilter(neighbourhoodId: #{other_address.neighbourhood_id}) { + id + name + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + # puts JSON.pretty_generate(result.as_json) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 5, 'was expecting to see only events from other_partner' + end + + test 'can scope to neighbourhood (via partner service area)' do + + neighbourhood_good = neighbourhoods(:one) + neighbourhood_bad = neighbourhoods(:two) + + @partner.service_areas.create! neighbourhood: neighbourhood_good + @partner.update! address: nil + + 5.times do + @partner.events.create!( + dtstart: Date.today, + summary: "partner in good neighbourhood: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + end + + other_partner = FactoryBot.build(:moss_side_partner, address: nil) + other_partner.service_areas.build neighbourhood: neighbourhood_bad + other_partner.save! + + 3.times do + other_partner.events.create!( + dtstart: Date.today, + summary: "partner in bad neighbourhood: An event summary", + description: 'Longer text covering the event in more detail', + address: FactoryBot.create(:bare_address_2) + ) + end + + query_string = <<-GRAPHQL + query { + eventsByFilter(neighbourhoodId: #{neighbourhood_good.id}) { + id + name + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + # puts JSON.pretty_generate(result.as_json) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 5, 'was expecting to see only events within neighbourhood_good service area' + end + # this should mainly be tested elsewhere + test 'can scope to tag (via partner tags)' do + + have_tag = FactoryBot.create(:tag) + have_not_tag = FactoryBot.create(:tag) + + @partner.tags << have_not_tag + + 2.times do + @partner.events.create!( + dtstart: Date.today, + summary: "partner 1: An event summary", + description: 'Longer text covering the event in more detail', + address: @address + ) + end + + + other_address = FactoryBot.create(:bare_address_2, neighbourhood: neighbourhoods(:two)) + other_partner = FactoryBot.create(:moss_side_partner, address: other_address) + + other_partner.tags << have_tag + + 6.times do + other_partner.events.create!( + dtstart: Date.today, + summary: "partner 2: An event summary", + description: 'Longer text covering the event in more detail', + address: other_address + ) + end + + query_string = <<-GRAPHQL + query { + eventsByFilter(tagId: #{have_tag.id}) { + id + name + } + } + GRAPHQL + + result = PlaceCalSchema.execute(query_string) + # puts JSON.pretty_generate(result.as_json) + assert result.has_key?('errors') == false, 'errors are present' + + data = result['data'] + assert data.has_key?('eventsByFilter'), 'Data structure does not contain event key' + + events = data['eventsByFilter'] + assert events.length == 6, 'was expecting to see only events from have_tag' + end end