Skip to content

Commit

Permalink
Merge pull request #980 from rubyforgood/hmm/spike-of-community-resou…
Browse files Browse the repository at this point in the history
…rces

Spike of community resource browsing
  • Loading branch information
solebared authored Dec 10, 2021
2 parents cdd0b90 + 5270467 commit f2322a3
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 25 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ We're still in the [initial development phase](https://www.jering.tech/articles/
This changelog also serves to acknowledge the incredible people who've contributed brilliance, effort and being. Their handles are listed under the first release they each touched. 💗🙏🏾

## [Unreleased]

### Enhancements
* Show Community Resources in the same contributions page as all the other contributions

### Security vulnerabilities fixed
* Fix bug in authorization logic that allows a guest user to view names, emails, addresses of other people #951
* We recommend all installations update to this release IMMEDIATELY.
Expand Down
17 changes: 13 additions & 4 deletions app/blueprints/contribution_blueprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
class ContributionBlueprint < Blueprinter::Base
identifier :id
association :categories_for_tags, name: :category_tags, blueprint: DefaultBlueprint
association :service_area, blueprint: ServiceAreaBlueprint, view: :with_location
association :service_areas, blueprint: ServiceAreaBlueprint, view: :with_location
association :contact_types, blueprint: DefaultBlueprint do |contribution, _options|
[contribution.person.preferred_contact_method]
[contribution.preferred_contact_method]
end
association :urgency, blueprint: DefaultBlueprint do |contribution, _options|
UrgencyLevel.find(contribution.urgency_level_id)
Expand All @@ -16,9 +16,18 @@ class ContributionBlueprint < Blueprinter::Base
contribution.created_at.to_f * 1000 # Javascript wants miliseconds, not seconds
end
field :type, name: :contribution_type

field :key do |contribution|
"#{contribution.type.parameterize.underscore}-#{contribution.id}"
end
field :view_path do |contribution, options|
routes.contribution_path(contribution.id) if options[:show_view_path]
# FIXME: ugly conditional here requires some cleaning up of our contributon, listing and community resource models
if options[:show_view_path]
if contribution.type == "Community Resource"
routes.community_resource_path(contribution.id)
else
routes.contribution_path(contribution.id)
end
end
end
field :match_path do |contribution, options|
routes.match_listing_listing_path(contribution.id) if options[:show_match_path]
Expand Down
4 changes: 4 additions & 0 deletions app/filters/contact_method_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def self.filter_grouping

def filter(scope)
return super unless parameters

# We may want to add contact method logic for community resources. For now, this effectively allows all
return super unless scope.respond_to? :person_id

scope.joins(:person).where(people: {preferred_contact_method: parameters.keys})
end
end
5 changes: 3 additions & 2 deletions app/filters/contribution_type_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ class ContributionTypeFilter < BaseFilter
def self.filter_grouping
{name: 'Contribution Types', filter_options: [
{id: 'ContributionType[Ask]', name: 'Ask'},
{id: 'ContributionType[Offer]', name: 'Offer'}
{id: 'ContributionType[Offer]', name: 'Offer'},
{id: 'ContributionType[CommunityResource]', name: 'Community Resource'}
]}
end
ALL_ALLOWED_TYPES = ['Ask', 'Offer'].freeze
ALL_ALLOWED_TYPES = %w[Ask Offer CommunityResource].freeze

def filter(scope)
raise NotImplementedError.new(
Expand Down
2 changes: 1 addition & 1 deletion app/filters/service_area_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ def self.filter_grouping

def filter(scope)
return super unless parameters
scope.where(service_area_id: parameters.keys)
scope.in_service_areas(parameters.keys)
end
end
12 changes: 8 additions & 4 deletions app/javascript/pages/browse/ListBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<th>View</th>
<th v-if="showContributorNames">Contributor Name</th>
</tr>
<tr v-for="contribution in contributions" :key="contribution.id">
<tr v-for="contribution in contributions" :key="contribution.key">
<td>
<MappedIconList :iconTypes="contribution.inexhaustible ? [{name: contribution.contribution_type}, {name: 'Inexhaustible'}] : [{name: contribution.contribution_type}]" />
</td>
Expand All @@ -23,7 +23,11 @@
{{ contribution.urgency.name }}
</b-tag>
</td>
<td>{{ contribution.service_area.name }}</td>
<td>
<div v-for="service_area in contribution.service_areas" :key="service_area.id" class="has-text-grey-lighter">
{{ service_area.name }}
</div>
</td>
<td style="text: nowrap;">
<SingleIcon :iconType="contribution.contact_types[0].name" />
</td>
Expand All @@ -32,7 +36,7 @@
<a :href="contribution.view_path" class="button icon-list is-primary"><span class=""> View</span></a>
</div>
</td>
<td v-if="showContributorNames">{{ contribution.person.name }}</td>
<td v-if="showContributorNames ">{{ !!contribution.person ? contribution.person.name : ' ' }}</td>
</tr>
</table>
</div>
Expand All @@ -55,7 +59,7 @@ export default {
},
computed: {
showContributorNames() {
return this.contributions.some(contribution => contribution.person.name)
return this.contributions.some(contribution => contribution.person && contribution.person.name)
},
},
}
Expand Down
13 changes: 9 additions & 4 deletions app/javascript/pages/browse/MapBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
filters: Object,
},
components: { Mapbox },
computed: {
mappableContributions() {
return this.contributions.filter((c) => { return c.location || c.service_areas && c.service_areas.length > 0 })
}
},
methods: {
zoomend(map, e) {
console.log('Map zoomed')
Expand Down Expand Up @@ -73,9 +78,9 @@
contribution.location && contribution.location.street_address
? contribution.location.street_address
: ''
} ${contribution.service_area.location.city} ${
contribution.service_area.location.state
} ${contribution.service_area.location.zip_code}`,
} ${contribution.service_areas[0].location.city} ${
contribution.service_areas[0].location.state
} ${contribution.service_areas[0].location.zip}`,
limit: 1,
})
.send()
Expand Down Expand Up @@ -154,7 +159,7 @@
map.fitBounds(bounds, {padding: 50})
},
loaded: function () {
this.geocode(this.contributions).then((geojson) => this.add_markers(geojson, this.map))
this.geocode(this.mappableContributions).then((geojson) => this.add_markers(geojson, this.map))
},
initialized: function (map) {
this.map = map
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/pages/browse/TileBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ul class="columns is-multiline is-centered">
<TileListItem
v-for="contribution in contributions"
:key="contribution.id"
:key="contribution.key"
v-bind="contribution"
class="column is-one-quarter"
/>
Expand Down
6 changes: 3 additions & 3 deletions app/javascript/pages/browse/TileListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
</div>
<h5 class="has-text-weight-bold">{{ title }}</h5>
<p>{{ description }}</p>
<div v-if="service_area" class="has-text-grey-lighter">
<div v-for="service_area in service_areas" :key="service_area.id" class="has-text-grey-lighter">
{{ service_area.name }}
</div>
<div v-if="person.name" class="contributor-name">
<div v-if="person && person.name" class="contributor-name">
{{ `From: ${person.name}` }}
</div>
</div>
Expand All @@ -55,7 +55,7 @@ export default {
props: {
contribution_type: String,
category_tags: {type: Array, default: () => []},
service_area: {type: Object, default: null},
service_areas: {type: Array, default: () => []},
title: String,
inexhaustible: Boolean,
description: String,
Expand Down
37 changes: 36 additions & 1 deletion app/models/community_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ class CommunityResource < ApplicationRecord

has_many :matches_as_receiver
has_many :matches_as_provider
has_many :community_resource_service_areas
has_many :community_resource_service_areas, dependent: :destroy
has_many :service_areas, through: :community_resource_service_areas

validates :name, :description, :publish_from, presence: true

# We're not sure if we need or want these validations. Adding them now assuming it will be easier to remove them later than the other way around
validates :service_areas, presence: true
validates_associated :service_areas
validates :tag_list, presence: true

accepts_nested_attributes_for :organization

scope :approved, -> { where(is_approved: true) }
scope :pending_review, -> { where(is_approved: false) }
# TODO: add tests for this?
scope :in_service_areas, ->(ids) { joins(:service_areas).where(service_areas: {id: ids}).distinct }

def self.published
before_now = DateTime.new..Time.current
Expand All @@ -31,20 +38,48 @@ def self.published
)
end

def title; description; end
def self.matchable; published; end

def published?
now = Time.current
is_approved &&
(publish_from.present? ? publish_from <= now : true) &&
(publish_until.nil? || now < publish_until)
end

def categories_for_tags
Category.where(name: tag_list)
end

def all_tags_unique
all_tags_list.flatten.map(&:downcase).uniq
end

def all_tags_to_s
all_tags_unique.join(', ')
end

def preferred_contact_method
# TODO: This is a hack that makes things work for now
# The test creates a contact method that will match this
# and the dev db seeding creates a couple, too
ContactMethod.method_name('call').last
end

def type
"Community Resource"
end

def inexhaustible
true
end

def urgency_level_id
UrgencyLevel::TYPES.last.id
end

def person; end
end

# == Schema Information
Expand Down
9 changes: 9 additions & 0 deletions app/models/listing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Listing < ApplicationRecord
scope :match_status, ->(match_status) { where(state: match_status.to_s) }
scope :person_id, ->(person_id) { where(person_id: person_id.to_i) }

scope :in_service_areas, ->(ids) { where(service_area_id: ids) }
scope :service_area_name, ->(service_area_name) {
includes(service_area: :mobility_string_translations)
.references(:mobility_string_translations)
Expand Down Expand Up @@ -116,9 +117,17 @@ def categories_for_tags
Category.where(name: tag_list)
end

def service_areas
[service_area]
end

def has_email?
person.email.present?
end

def preferred_contact_method
person.preferred_contact_method
end
end

# == Schema Information
Expand Down
33 changes: 31 additions & 2 deletions spec/blueprints/contribution_blueprint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
contribution.service_area.save!
expected_data = {'contributions' => [{
'id' => contribution.id,
'key' => "ask-#{contribution.id}",
'contribution_type' => 'Ask',
'category_tags' => [{'id' => expected_category_id, 'name' => expected_category}],
'inexhaustible' => contribution.inexhaustible,
Expand All @@ -45,7 +46,7 @@
'name' => contribution.person.preferred_contact_method.name
}
},
'service_area' => {
'service_areas' => [{
'description' => contribution.service_area.description,
'id' => contribution.service_area.id,
'name' => expected_area_name,
Expand All @@ -59,7 +60,7 @@
'street_address' => contribution.service_area.location.street_address,
'zip' => contribution.service_area.location.zip
}
},
}],
# "map_location" => "44.5,-85.1",
'title' => contribution.title,
'description' => contribution.description,
Expand All @@ -74,4 +75,32 @@
result = ContributionBlueprint.render(contribution, show_view_path: true, current_user: user)
expect(JSON.parse(result)['view_path']).to eq(expected_path)
end

it 'can serialize a community resource as a contribution' do
resource = create(:community_resource, tag_list: expected_category)
# The test database defaults to having no contact methods, so we need at least one
default_contact_method = create(:contact_method)
expected_result_without_service_area = {
"id" => resource.id,
"key" => "community_resource-#{resource.id}",
"category_tags" => [{"id" => expected_category_id, "name" => expected_category}],
"contact_types" => [{"id" => default_contact_method.id, "name" => "Call"}],
"contribution_type" => "Community Resource",
"created_at" => resource.created_at.to_f * 1000,
"description" => "Food for the revolution",
"inexhaustible" => true,
"location" => nil,
"match_path" => nil,
"name" => "Free breakfast for School Children Program",
"person" => nil,
"title" => "Food for the revolution",
"urgency" => {"id" => 4, "name" => "Anytime"},
"view_path" => "/community_resources/#{resource.id}"
}
result = JSON.parse(ContributionBlueprint.render(resource, current_user: user, show_view_path: true))
result_without_service_areas = result.dup
result_without_service_areas.delete("service_areas")
expect(result_without_service_areas).to eq(expected_result_without_service_area)
expect(result["service_areas"].first["id"]).to eq(resource.service_areas.first.id)
end
end
2 changes: 2 additions & 0 deletions spec/factories/community_resources.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
name { 'Free breakfast for School Children Program' }
description { 'Food for the revolution' }
publish_from { Date.current }
service_areas { [association(:service_area)] }
tag_list { [association(:category).name] }
end
end

Expand Down
6 changes: 5 additions & 1 deletion spec/forms/community_resource_form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

RSpec.describe CommunityResourceForm do
let!(:location_type) { create :location_type }
let!(:service_area) { create :service_area }
let(:category) { create :category }
let(:params) do
{
name: 'Free breakfast program',
Expand All @@ -13,7 +15,9 @@
},
organization_attributes: {
name: 'Black Panther Party'
}
},
service_area_ids: [service_area.id],
tag_list: [category.name]
}
end

Expand Down
19 changes: 18 additions & 1 deletion spec/models/community_resource_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'rails_helper'

RSpec.describe CommunityResource do
RSpec.describe CommunityResource, type: :model do
describe "community resource can have multiple service areas" do
it "has many service areas" do
should respond_to(:service_areas)
Expand All @@ -9,4 +9,21 @@
should_not respond_to(:service_area)
end
end

describe "validation" do
it "can be valid" do
expect(build(:community_resource)).to be_valid
end

it "is invalid if missing a service area" do
subject = build(:community_resource)
subject.service_areas = []
expect(subject).to_not be_valid
end

it "is invalid if missing a tag" do
subject.tag_list = []
expect(subject).to_not be_valid
end
end
end
Loading

0 comments on commit f2322a3

Please sign in to comment.