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

Change stats layout #3512

Merged
merged 82 commits into from
May 21, 2019
Merged

Change stats layout #3512

merged 82 commits into from
May 21, 2019

Conversation

javierm
Copy link
Member

@javierm javierm commented May 20, 2019

References

Objectives

  • Include stats by gender, age and geozone for both polls and budgets
  • Unify the code and the interface of budget and poll stats
  • Use an improved layout for stats

Visual Changes

Before these changes:

Generic and advanced budget stats are shown together

After these changes:

Gender and age stats are displayed first, and there's a menu to navigate through the stats

Advanced stats are displayed after generic stats

Release notes

⚠️ If you'd like to generate stats for your existing polls and budgets, execute:

RAILS_ENV=production bin/rake stats:generate

app/views/shared/stats/_participation.html.erb Outdated Show resolved Hide resolved
app/views/polls/stats.html.erb Outdated Show resolved Hide resolved
app/views/polls/stats.html.erb Outdated Show resolved Hide resolved
percentage_participants_all_phase: participants_percent(heading_totals, groups_totals, :total_participants_all_phase),
percentage_district_population_all_phase: population_percent(population, heading_totals[:total_participants_all_phase])
percentage_participants_every_phase: participants_percent(heading_totals, groups_totals, :total_participants_every_phase),
percentage_district_population_every_phase: population_percent(population, heading_totals[:total_participants_every_phase])

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [131/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

@@ -122,8 +151,8 @@ def calculate_heading_stats_with_totals(heading_totals, groups_totals, populatio
percentage_district_population_support_phase: population_percent(population, heading_totals[:total_participants_support_phase]),
percentage_participants_vote_phase: participants_percent(heading_totals, groups_totals, :total_participants_vote_phase),
percentage_district_population_vote_phase: population_percent(population, heading_totals[:total_participants_vote_phase]),
percentage_participants_all_phase: participants_percent(heading_totals, groups_totals, :total_participants_all_phase),
percentage_district_population_all_phase: population_percent(population, heading_totals[:total_participants_all_phase])
percentage_participants_every_phase: participants_percent(heading_totals, groups_totals, :total_participants_every_phase),

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [130/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

groups[:total][:total_participants_every_phase] = groups.collect {|_k, v| v[:total_participants_every_phase]}.sum

budget.headings.each do |heading|
groups[heading.id].merge!(calculate_heading_stats_with_totals(groups[heading.id], groups[:total], heading.population))

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [124/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

groups[:total][:total_investments_count] = groups.collect {|_k, v| v[:total_investments_count]}.sum
groups[:total][:total_participants_support_phase] = groups.collect {|_k, v| v[:total_participants_support_phase]}.sum
groups[:total][:total_participants_vote_phase] = groups.collect {|_k, v| v[:total_participants_vote_phase]}.sum
groups[:total][:total_participants_every_phase] = groups.collect {|_k, v| v[:total_participants_every_phase]}.sum

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [117/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

groups[:total] = Hash.new(0)
groups[:total][:total_investments_count] = groups.collect {|_k, v| v[:total_investments_count]}.sum
groups[:total][:total_participants_support_phase] = groups.collect {|_k, v| v[:total_participants_support_phase]}.sum
groups[:total][:total_participants_vote_phase] = groups.collect {|_k, v| v[:total_participants_vote_phase]}.sum

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [115/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

budget.ballots.pluck(:ballot_lines_count).inject(0) { |sum, x| sum + x }
groups[:total] = Hash.new(0)
groups[:total][:total_investments_count] = groups.collect {|_k, v| v[:total_investments_count]}.sum
groups[:total][:total_participants_support_phase] = groups.collect {|_k, v| v[:total_participants_support_phase]}.sum

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [121/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

def total_votes
budget.ballots.pluck(:ballot_lines_count).inject(0) { |sum, x| sum + x }
groups[:total] = Hash.new(0)
groups[:total][:total_investments_count] = groups.collect {|_k, v| v[:total_investments_count]}.sum

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [103/100] (https://github.com/bbatsov/ruby-style-guide#80-character-limits)

@javierm javierm force-pushed the backport-gender_participants branch from 15e4d27 to f754740 Compare May 20, 2019 12:57
@javierm javierm force-pushed the backport-stats branch 2 times, most recently from 361e2ac to 5b737ed Compare May 20, 2019 18:38
Copy link
Member

@voodoorai2000 voodoorai2000 left a comment

Choose a reason for hiding this comment

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

👏

javierm and others added 18 commits May 21, 2019 13:50
We would now like to differenciate between 70-year-old people and
90-year-old people.
This way we can easily see the h3 tag's parent is the h2 tag.
We try to make the method return data which is easier to handle in the
view.
The rest of the `before` block still uses instance variables, but at
least the rest of the file doesn't use instance variables anymore.
So related methods are on the same line.
Naming it "Dropdown" was misleading.
Turbolinks doesn't get on well with Foundation's Sticky, and so we need
to manually trigger the event on Turbolinks' `page:load`.
javierm added 24 commits May 21, 2019 13:50
The code is easier to read now, it returns the same results it used to
return, and performance-wise it's probably the same thing, but if it's
not, we'll trust Rails will do optimizations that we don't when we
manually pluck the IDs.
We currently don't store geozone population.
Even if this class looks very simple now, we're trying a few things
related to these stats. Having a class for it makes future changes
easier and, if there weren't any future changes, at least it makes
current experiments easier.

Note we keep the method `participants_by_geozone` to return a hash
because we're caching the stats and storing GeozoneStats objects would
need a lot more memory and we would get an error.
If there's demographic data for all participants, it doesn't make sense
to show the message.

We're using translations instead of an `if` in the view because the text
is also different when there's only one participant. In some languages
the text might also be different depending on how many people with no
demographic data participated.

Another possibility would be to use an `if` in the view so we don't
display an empty paragraph when the cont is zero, and then using
translation for `one` and `other`. I haven't gone that way because I
thought the logic would be more complex and the benefits wouldn't be
that great.
As the Rails guides say:

> All scope methods will return an ActiveRecord::Relation object

That means `find_by_kind` will return a relation when nothing is found;
the expected behaviour is to return `nil`, like all `find_by` methods
do.

Using scopes also means strange things happen when we try to chain
scopes like `phases.published.drafting`. With scopes, the `drafting`
part would be ignored and all published phases would be returned.
This implementation is a bit more robust because we don't have to change
any of the "or_later?" methods if we add or remove a new phase.

We could also use metaprogramming to reduce code duplication in these
methods. So far, I've decided to keep the code simple since the
duplication seems reasonable.
We're going to use it so we know if a budget has finished its support
phase.
So these styles are available in CONSUL.

Note we're not including these styles inside `.participation-stats`
because this class is used in Plaza de España's statistics.
"All phase" doesn't sound right in English, and we're going to refactor
the code related to the phases.
This way we can show statistics for the supports phase before the vote
phase is over.
This way we recalculate all data including the participants in the phase
which has just finished.
This way we can share the `participants` method between budget and poll
stats.
If users participated and were hidden after participating, we should
still count them in the participants stats.

In the tests, we set users' `hidden_at` attribute before they vote.
Although in real life they would vote first and then they would be
hidden, I've written the tests like this for the sake of simplicity.
We need a way to manually expire the cache for a budget or poll without
expiring the cache of every budget or poll.

Using the `updated_at` column would be dangerous because most of the
times we update a budget or a poll, we don't need to regenerate their
stats.

We've considered adding a `stats_updated_at` column to each of these
tables. However, in that case we would also need to add a similar column
in the future to every process type whose stats we want to generate.
These methods are only used while stats are being generated; once stats
are generated, they aren't used anymore. So there's no need to store
them using the Dalli cache.

Furthermore, there are polls (and even budgets) with hundreds of
thousands of participants. Calculating stats for them takes a very long
time because we can't store all those records in the Dalli cache.

However, since these records aren't used once the stats are generated,
we can store them in an instance variable while we generate the stats,
speeding up the process.
We're generating stats every 2 hours because it's less than the time it
will take to generate stats for every process. Once stats are generated,
this task should take less than a second.

The regenerate task has been added so we can manually execute it.
Using SQL's `select` instead of converting the records to a ruby array
increases performance dramatically when there are thousands of records.
For a poll with 200000 voters, calculating stats took more than 7
minutes, and now it takes less than 2 minutes.
Methods defined inside "included" cannot be called using `super` from
a class including the module.
Due to technical issues, sometimes users voted in booths and their vote
couldn't be added to the database. So we're including them in the users
with no demographic data.
We had forgotten to update these tags for stats after updating them for
budget results in commit 153b46b and commit a6baaa9.
@javierm javierm changed the base branch from backport-gender_participants to master May 21, 2019 15:18
@javierm javierm merged commit 82e3c41 into master May 21, 2019
@javierm javierm deleted the backport-stats branch May 21, 2019 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants