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

[Issue 2899] Distribution by counties! #3328

Merged
merged 27 commits into from
May 5, 2023

Conversation

cielf
Copy link
Collaborator

@cielf cielf commented Jan 15, 2023

Resolves #2899

Description

Ability to have show the distribution by counties. at a high level.

Addition of optional areas_served partial to partner profile. This allows partners to select the counties they serve, and assign a percentage to each.
Distribution by county 'report'. Note this is not currently exportable.
Button leading to distribution by county report added to dashboard
Import of US counties


NOTE: The Import of US counties job ( load_us_counties) will have to be run when this is promoted !!!!!!!


Some decisions I'm not 100% sure are the best:
1/ County has full name (e.g. Washington County, Vermont) and region (Vermont) only -- maybe it should have the short name (without region) also. But I think we could run a job to extract it in the future if we have need. Chose region rather than state, because we include US territories.
2/ I chose show, rather than index or something else for the action for distribution_by_county. It is an endpoint rather than a jumping off point, though I can imagine a world where we would want to have links on each county. Will show be confusing - i.e. will people think there is a corresponding ActiveRecord?
3/ Not 100% happy with the name "served_area" for the model. Service areas? Served_areas at least matches up with areas_served, which is the title of the partial. I thought about using client_share as the name, but served_area allows the possibility of having other things about the area in the future better.

I do expect we will be asked to expand this in the future, to show (for example) diapers by county. But let's see what happens -- that should not require more input.

Dependency:

gem "activerecord-import"
And it does use js, including select2.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

New automatic tests
manual testing as I went along

Note that you will have to either bin/setup or just run the load_us_counties rake job in order to manually test .

@cielf cielf requested a review from dorner January 20, 2023 23:40
@cielf
Copy link
Collaborator Author

cielf commented Jan 20, 2023

Hey @dorner. Would love your review, but even if it passes muster, let's not merge it until what's in the current main gets deployed to production (I don't want any QA issues with this to hold up the current release.)

@cielf cielf requested a review from awwaiid February 6, 2023 20:35
Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

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

Wow, what a meaty update! Super impressed at all the work! Let me know if you have any questions about any of the comments.




function calculate_client_share_total(){
Copy link
Collaborator

Choose a reason for hiding this comment

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

The "new" way of doing things is using Stimulus controllers. You can look in app/javascript for some examples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah. I've taken a look at stimulus, and it's not obvious to me how to convert this. Might be a thing to pair on, but I'm inclined to let the thing that is working work - maybe put in a technical debt item for someone to work on converting both this and the distribution edits, which will be similar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It has been converted.


function calculate_client_share_total(){
total = 0;
console.log("*********mark 1 total is: " +total);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be console.debug or removed entirely.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Oversight. Will be corrected in next push.

function calculate_client_share_total(){
total = 0;
console.log("*********mark 1 total is: " +total);
const client_shares = document.querySelectorAll('*[id$="client_share"]')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kind of annoying, but JavaScript variables and functions should be in camelCase instead of snake_case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hm. Fine. Will be corrected in next push.

document.getElementById("partners-served-areas-client-share-total-warning").style.visibility= 'hidden';
// document.getElementById("profile-update-button").style.visibility = 'visible';
// document.getElementById("profile-update-button-disabled").style.visibility = 'hidden';
// document.getElementById("partner-county-client-share-total-warning-footer").style.visibility= 'hidden';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove commented out code if it's not going to be used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also removed in next push

def show
setup_date_range_picker
distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range)
breakdown_hash = {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This method is too long to be in a controller. It should be in a service class. I'd also recommend not using a hash at all here, but creating a strongly typed Struct to store these values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moving it to a service in the next push. Considering the advice re the struct. Will probably do that too, because my tests for the service revealed a whole lotta awkwardness.

namespace :db do
desc "Load US Counties"
task :load_us_counties => :environment do
if County.find_by(name:"Abbeville County", region: " South Carolina").nil?
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems kind of arbitrary - should we just do if County.none??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah... A situation where you need US counties and there are already counties, but none of them are US is rather unlikely [I can imagine it, because this is open source, but it's a stretch]. In next push.

Gemfile Outdated
@@ -35,6 +35,8 @@ gem "paper_trail"
gem "rolify", "~> 6.0"
# Enforces "safe" migrations.
gem "strong_migrations", "1.4.1"
# imports (used to set up counties)
gem "activerecord-import"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alrighty. That's the last thing I'm adding in the "low hanging fruit" push I'll be doing shortly.

describe "show" do
it "returns http success" do
get distributions_by_county_show_path(default_params)
expect(response).to have_http_status(:success)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we have some additional tests besides just "it doesn't error"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a bunch in on the service in the next push. Will that do?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just some check for a particular string, e.g. the county and distribution name?

end
end

context "handles time ranges properly (not fully written yet)" do
Copy link
Collaborator

Choose a reason for hiding this comment

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

does this need to be fixed up?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just the context title. Next push.

@dorner
Copy link
Collaborator

dorner commented Feb 8, 2023

With re the questions above:

  1. You can add a migration that calls the Rake task.
  2. It probably would be best to name it anything other than show since there is no corresponding model. You could even just call it distributions_by_county again.
  3. I think ServedArea is a bit clunky but clear in what it represents. I don't think it needs to be changed.

@cielf
Copy link
Collaborator Author

cielf commented Feb 8, 2023

Thanks, @dorner! I hope to address most of these this week, FYI. (though there is a lot, and it requires learning...)

@cielf
Copy link
Collaborator Author

cielf commented Feb 17, 2023

I think I've caught everything except the stimulus controller -- which is not yielding easily.

@cielf
Copy link
Collaborator Author

cielf commented Feb 17, 2023

The tests are all passing on local.

@cielf cielf requested a review from dorner February 24, 2023 16:04
@cielf cielf changed the title [Issue 2899] Distribution by counties! [Issue 2899] [WIP] Distribution by counties! Feb 24, 2023
@cielf
Copy link
Collaborator Author

cielf commented Feb 24, 2023

Alas, there is still a problem with this -- the deletion is not working properly. Putting it to a WIP state.

…"removed" item zero, plus a little rubocop happiness
@cielf cielf changed the title [Issue 2899] [WIP] Distribution by counties! [Issue 2899] Distribution by counties! Feb 24, 2023
@cielf
Copy link
Collaborator Author

cielf commented Feb 24, 2023

Ok. The problem with the removal of served areas has been solved -- I'm a little concerned with my solution, though. With the data-actions on delete_served_area_button in ui_helper.rb, can we be sure that the first click will complete before the second one starts, or are we going to have to go down the road of zeroing the value, and then somehow calling the enveloping controller. It seems to be working, but my sample set is small.

@cielf
Copy link
Collaborator Author

cielf commented Feb 24, 2023

I'm also getting a fair number of cases where these new tests are failing when I run the whole test suite. It seems to be related to Faker.Address.unique.community calls. They always pass when I "Rerun Failed Tests". Setting it aside for the nonce, as I have some meaty support stuff to work on, but will want to revisit. It is not always the same test.

@cielf
Copy link
Collaborator Author

cielf commented Feb 27, 2023

Per @awwaiid's vocal request, I've merged main in, and found one of the tests that sometimes fail as an example: the test is at ./spec/models/partners/served_area_spec.rb:23 and the error is
Faker::UniqueGenerator::RetryLimitExceeded: Retry limit exceeded for community

@dorner
Copy link
Collaborator

dorner commented Feb 28, 2023

This just reinforces my dislike of using randomly generated data in tests. If there is a failure, you can't reliably reproduce it, and it makes writing the tests themselves a lot harder.

# end
# end
# end
RSpec.describe DistributionsByCountyHelper, type: :helper do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Delete this file too if you delete the other

context "no served areas" do
let(:profile) { FactoryBot.build(:partner_profile) }
it "has 0 client share" do
expect(profile.client_share_total).to eq(0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suggest a expect(profile.valid?).to eq(true) here too, as below.

cielf and others added 3 commits March 27, 2023 10:14
Co-authored-by: Brock Wilcox <awwaiid@thelackthereof.org>
Co-authored-by: Brock Wilcox <awwaiid@thelackthereof.org>
@cielf cielf requested a review from awwaiid April 2, 2023 16:22
@dorner
Copy link
Collaborator

dorner commented Apr 14, 2023

@cielf is this ready for re-review? Haven't looked at it in a while.

@cielf
Copy link
Collaborator Author

cielf commented Apr 14, 2023

@dorner Yes. I believe it is.

Copy link
Collaborator

@awwaiid awwaiid left a comment

Choose a reason for hiding this comment

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

Everything looks good to me; I think getting it into user hands to see what feedback they have sounds good.

Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

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

Looking great! A few further suggestions, but they are on the minor side.

}
calculateClientShareTotal(){
let total = 0;
let share_targets = this.shareTargets
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line isn't really necessary since the next line can just be this.shareTargets.forEach(

this.warningTarget.style.color= 'red';
}
return total;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

In general there are quite a lot of spacing fixes I'd put here (we don't have a JS linter):

  • A space before and after =>
  • A space before and after { and } 
  • A space before ( and after )
  • A space before and after =

Copy link
Collaborator

Choose a reason for hiding this comment

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

Still missing some spaces before = 😄

app/models/partners/profile.rb Outdated Show resolved Hide resolved
errors.add(:base, "Total client share must be 0 or 100")
end
check
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can be tidied up:

def client_share_is_0_or_100
  value = client_share_total
  if value != 0 && value != 100
    errors.add(:base, "Total client share must be 0 or 100")
  end
end


class DistributionByCountyReportService
def get_breakdown(distributions)
breakdown_struct = Struct.new(:name, :region, :num_items, :amount)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd have this as a constant above the method rather than defined in the method:

Breakdown = Struct.new(:name, :region, :num_items, :amount)
...
breakdowns['Unspecified'] = Breakdown.new("Unspecified", "ZZZ", 0, 0.00)

@return_value = @partner.valid?

if @return_value
@profile.served_areas.each(&:destroy!)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can use destroy_all here.

describe "show" do
it "returns http success" do
get distributions_by_county_show_path(default_params)
expect(response).to have_http_status(:success)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just some check for a particular string, e.g. the county and distribution name?

partner_profile: @partner_1.profile, client_share: 20)
@partner_2.profile.served_areas[0].county = @partner_1.profile.served_areas[0].county
@partner_2.profile.served_areas[0].save
@partner_2.reload
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you're using instance variables here? Generally we prefer having data set up with let blocks or in a before block (if you don't need to reference them later).


it "shows 'Unspecified 100%' if no served_areas" do
@distribution = create(:distribution, :with_items, item: item_1, organization: @user.organization)
visit distributions_by_county_report_path
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we move most of these from system to request specs? System specs are only really necessary if we're testing multiple pages (via clicks) or JavaScript. Simple visits should be doable with request specs, which don't fire up headless Chrome and hence are much faster.

end
end

def setup_overlapping_partners
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be in a shared file somewhere since it's used in multiple specs?

cielf and others added 2 commits April 18, 2023 20:52
compressing summation

Co-authored-by: Daniel Orner <daniel.orner@wishabi.com>
@cielf
Copy link
Collaborator Author

cielf commented Apr 20, 2023

Hmmm.... that's odd -- it only did 1 check when I put the latest thing up -- I've not seen that before. Noting it to @dorner, and also requesting a re-review

@cielf cielf requested a review from dorner April 20, 2023 21:19
Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

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

Almost there! A couple more cleanup bits and I think we're ready!

}
calculateClientShareTotal ( ){
let total = 0;
this.share_targets.forEach(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry - this won't work 😅 Stimulus requires a specific format for the target variables - it has to follow the JavaScript "CamelCase" format rather than the Ruby "snake_case" format. So this has to read this.shareTargets.

this.warningTarget.style.color= 'red';
}
return total;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Still missing some spaces before = 😄

# business logic: the client share has to be 0 or 100 -- although it is an estimate only, making it 0 (not
# specified at all) or 100 means we won't have people overallocating (> 100) and that they think about what
# their allocation actually is
if client_share_total != 0 && client_share_total != 100
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we add a variable here to act as a cache? Otherwise it'll sum the client shares twice instead of once.

it "returns http success" do
get distributions_by_county_report_path(default_params)
expect(response).to have_http_status(:success)
expect(response.body).to include("Estimated Distributions by County for ")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Um - this doesn't have any actual data that's it checking for. Can we validate that we're at least viewing the page for the right distribution etc?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removing the plain jane test, as, if it would fail, the other tests will also fail.

context "basic behaviour with served areas" do
it "handles multiple partners with overlapping service areas properly" do
@distribution_p1 = create(:distribution, :with_items, item: item_1, organization: @user.organization, partner: partner_1, issued_at: issued_at_present)
@distribution_p2 = create(:distribution, :with_items, item: item_1, organization: @user.organization, partner: partner_2, issued_at: issued_at_present)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do these need to be instance variables? (Also line 30)

@cielf cielf requested a review from dorner April 23, 2023 17:42
Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

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

LGTM! Let's fix the Gemfile.lock and this is good to merge for me.

@cielf
Copy link
Collaborator Author

cielf commented Apr 28, 2023

Looks like we've got a real conflict here! We've taken somewhat different approaches, etc.

@cielf
Copy link
Collaborator Author

cielf commented Apr 28, 2023

Hey @dorner -- I think the test that is failing is actually in error -- that it should be checking for blankness rather than nilness. Shouldn't it?

@dorner
Copy link
Collaborator

dorner commented May 5, 2023

@cielf all looks good - now we have a conflict with schema.rb 😆

@cielf
Copy link
Collaborator Author

cielf commented May 5, 2023

@dorner Yeah. I can handle that. Just use the most recent one.

@cielf
Copy link
Collaborator Author

cielf commented May 5, 2023

Holding my breath and pushing merge! (We don't have anything else that's big in the pipe for this release)

@cielf cielf merged commit 320d29f into rubyforgood:main May 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Distributions by county report
3 participants