Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spike of community resource browsing #980

Merged
merged 16 commits into from
Dec 10, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 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 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
10 changes: 7 additions & 3 deletions app/javascript/pages/browse/ListBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 7 additions & 2 deletions app/javascript/pages/browse/MapBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
filters: Object,
},
components: { Mapbox },
computed: {
mappableContributions() {
return this.contributions.filter((c) => { return c.location || c.service_areas && c.service_areas.length > 0 })
}
},
methods: {
loaded(map) {
let geojson = {
Expand All @@ -47,9 +52,9 @@
return new Promise((resolve) => {
let forwarded = 0

this.contributions.forEach(contribution => {
this.mappableContributions.forEach(contribution => {
geoCodingService.forwardGeocode({
query: `${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}`,
query: `${contribution.location && contribution.location.street_address ? contribution.location.street_address : ""} ${contribution.service_areas[0].location.city} ${contribution.service_areas[0].location.state} ${contribution.service_areas[0].location.zip}`,
limit: 1
}).send()
.then(response => {
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
35 changes: 35 additions & 0 deletions app/models/community_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ class CommunityResource < ApplicationRecord

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
Comment on lines +20 to +23
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • TODO: fix request specs that now flunk validation checks


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)
h-m-m marked this conversation as resolved.
Show resolved Hide resolved
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
Comment on lines +64 to +67
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some way I can fix this in its own PR? In other codebases I work in, this whole workflow is something I'd hide behind a feature flag so I can get this PR out first, and then get the contact methods for community resources working in a separate PR. I could also try to fix it here, especially if anyone's willing to help me with it. Please let me know

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an issue for this at #986

end

def type
"Community Resource"
end

def inexhaustible
true
end

def urgency_level_id
UrgencyLevel::TYPES.last.id
end
Comment on lines +70 to +80
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@exbinary mentioned that these don't feel like actual attributes, behaviors, or properties of a community resource, and so they might be better off in an abstraction or interface class that turns community resources into something blueprint-able


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 @@ -112,9 +113,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
29 changes: 27 additions & 2 deletions spec/blueprints/contribution_blueprint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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 @@ -58,7 +58,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 @@ -73,4 +73,29 @@
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)
# The test database defaults to having no contact methods, so we need at least one
default_contact_method = create(:contact_method)
expected_result = {
"id" => resource.id,
"category_tags" => [],
"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,
Comment on lines +90 to +95
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@exbinary pointed out how hashes like this to test blobs of JSON are fragile since small changes can then require lots of edits

"service_areas" => [],
"title" => "Food for the revolution",
"urgency" => {"id" => 4, "name" => "Anytime"},
"view_path" => nil
}
result = ContributionBlueprint.render(resource, current_user: user)
expect(JSON.parse(result)).to eq(expected_result)
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
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