Commit Graph

20065 Commits

Author SHA1 Message Date
Javi Martín
fdf1fd5f5f Move links to check all/none on RTL languages
Since we don't usually style HTML classes starting with `js-`, we're
renaming it, so it's consistent with the `CheckAllNone` name used in the
`check_all_none.js` file.
2024-06-06 16:09:18 +02:00
Javi Martín
6ee5c0fcb8 Simplify expectations in "select all/none" tests 2024-06-06 16:09:18 +02:00
Javi Martín
f8f4054614 Make "select all/none" tests actually test something
Since we were on the "Pending review" filter, and there were no records
pending review, the code checking all checkboxes were checked/unchecked
didn't test anything because there were no checkboxes on the page.

So we're clicking on the "All" filter first.
2024-06-06 16:09:18 +02:00
Javi Martín
26a43ee0d2 Add a proper title to moderation index pages
Since this is already a component, we can use the `header` method
without much refactoring.
2024-06-06 16:09:18 +02:00
Javi Martín
75598972e3 Merge pull request #5570 from consuldemocracy/dependabot/bundler/rails-7.0.8.4
Bump rails from 7.0.8.1 to 7.0.8.4
2024-06-05 18:30:11 +02:00
dependabot[bot]
018b4aabbe Bump rails from 7.0.8.1 to 7.0.8.4
Bumps [rails](https://github.com/rails/rails) from 7.0.8.1 to 7.0.8.4.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v7.0.8.1...v7.0.8.4)

---
updated-dependencies:
- dependency-name: rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-05 15:44:31 +00:00
Javi Martín
1e2c78f3dc Merge pull request #5243 from consuldemocracy/tenant_locales
Allow different locales per tenant
2024-06-05 16:27:02 +02:00
Javi Martín
0c59c2dfb4 Extract model to handle locales settings
This way we can simplify the view, particularly the form. However, we're
still adding some complexity to the form so inputs are inside labels and
so the collection is easier to style with CSS.
2024-06-05 16:10:56 +02:00
Javi Martín
2596b4e78b Use multiple columns to display languages options
When the list of available languages becomes too large, having all of
them in one column makes it harder to select them.

What we're trying to do here is to have multiple columns, with each
column containing between 5 and 10 options, so they can all be easily
seen on the screen at the same time.

For that, there are mainly three options: a flex layout, a grid layout
or a multi-column layout.

Since languages are ordered (more or less) alphabetically, the natural
way to display them is showing the first few ones on the first column,
the following ones on the second column, and so on, as opposed to
displaying the first ones on the first row, the following ones on the
secord row, ... AFAIK this can't be accomplished using a flex layout.

I've tried to do so using a grid layout, and failed. The problem here is
that we don't know how many rows we're going to have.

So we're using a multi-column layout. I haven't found a way to guarantee
a minimum height for the content of each column, so in the end we're
using a hack with the `:has` pseudoclass. Note this pseudoclass is only
supported by about 92%-94% of the browsers (including the last few
versions of all major browsers); people using other browsers will still
see all the options on one column, just like people using small screens
do.
2024-06-05 16:10:56 +02:00
Javi Martín
78bbf430d5 Add a form to edit available locales
We're using different controls depending on the number of available
locales.

When there are only a few locales, the solution is obvious: radio
buttons to select the default language, and checkboxes to select the
available ones are simple and intuitive.

With many languages, showing two consecutive lists of 30 languages could
be confusing, though, particularly on small devices, where scrolling
through both lists could be hard.

So, in this case, we're rendering a <select> to choose the default
language. For selecting the available languages, however, we're sticking
to checkboxes because all the other existing options (like multiple
selects) are hard to use. We think it's OK because the form doesn't have
any additional fields, and there's only one big list of options to
scroll through.

While testing the application, we noticed that if we use the
`admin-fieldset-separator` styles when there's only one fieldset, it's
harder to notice that there's an additional field to select the default
language. So we're only using the `admin-fieldset-separator` styles when
all the fields are grouped in fieldsets.

Regarding the help text for the fieldset, if we leave the help text
outside the <legend> tag, people using screen readers won't hear about
this content. However, if we include it inside the <legend> tag, some
screen readers might read it every time they move to a different
checkbox (or radio button), which can be annoying. Since I don't think
these help messages are really essential, I'm leaving them out of the
<legend> tag. It's also easier to style them if they're outside the
<legend> tag.

Note we're using `display: table` for the labels, for the reasons
mentioned in commit 923c2a7ee.

Also note that, when there's only one available locale, this section is
useless. In this case, we aren't disabling it for now because there's a
chance people see it in the official Consul Democracy demo and then
wonder why it isn't available on their installation. We might disable it
in the future, though.
2024-06-05 16:10:56 +02:00
Javi Martín
999d5c2f67 Remove redundant "Manage" in admin menu entries
This is the admin section; it's obvious that every link in the menu will
take you to a page to manage something.

We're going to add a new item to either the "Settings" or the "Site
content" section, so it's a good time to improve what's already there.
2024-06-05 16:10:56 +02:00
Javi Martín
6de4737b70 Allow different default locales per tenant
Note that, for everything to work consistently, we need to make sure
that the default locale is one of the available locales.

Also note that we aren't overwriting the `#save ` method set by
globalize. I didn't feel too comfortable changing a monkey-patch which
ideally shouldn't be there in the first place, I haven't found a case
where `Globalize.locale` is `nil` (since it defaults to `I18n.locale`,
which should never be `nil`), so using `I18n.default_locale` probably
doesn't affect us.
2024-06-05 16:10:56 +02:00
Javi Martín
722e50a669 Simplify code to find a content block
We're only calling the `block_for` method from one place: the
`content_block` helper, and we're never passing the locale parameter to
that helper, which means we're always calling `block_for` using
`I18n.locale`.

So I'm not sure why we were doing the `locale ||=` assignment, since
`I18n.locale` doesn't return nil.

In any case, since this was added in commit c1de2dced, Ruby has added
support for arguments forwarding, so we can use it here to simplify the
code a little bit.
2024-06-05 16:10:56 +02:00
Javi Martín
647121d13e Allow different locales per tenant
Note that, currently, we take these settings from the database but we
don't provide a way to edit them through the admin interface, so the
locales must be manually introduced through a Rails console.

While we did consider using a comma-separated list, we're using spaces
in order to be consistent with the way we store the allowed content
types settings.

The `enabled_locales` nomenclature, which contrasts with
`available_locales`, is probably subconsciously based on similar
patterns like the one Nginx uses to enable sites.

Note that we aren't using `Setting.enabled_locales` in the globalize
initializer when setting the fallbacks. This means the following test
(which we could add to the shared globalizable examples) would fail:

```
it "Falls back to an enabled locale if the fallback is not enabled" do
  Setting["locales.default"] = "en"
  Setting["locales.enabled"] = "fr en"
  allow(I18n.fallbacks).to receive(:[]).and_return([:fr, :es])
  Globalize.set_fallbacks_to_all_available_locales

  I18n.with_locale(:fr) do
    expect(record.send(attribute)).to eq "In English"
  end
end
```

The reason is that the code making this test pass could be:

```
def Globalize.set_fallbacks_to_all_available_locales
  Globalize.fallbacks = I18n.available_locales.index_with do |locale|
    ((I18n.fallbacks[locale] & Setting.enabled_locales) + Setting.enabled_locales).uniq
  end
end
```

However, this would make it impossible to run `rake db:migrate` on new
applications because the initializer would try to load the `Setting`
model but the `settings` table wouldn't exist at that point.

Besides, this is a really rare case that IMHO we don't need to support.
For this scenario, an installation would have to enable a locale, create
records with contents in that locale, then disable that locale and have
that locale as a fallback for a language where content for that record
wasn't created. If that happened, it would be solved by creating content
for that record in every enabled language.
2024-06-05 16:10:56 +02:00
Javi Martín
790d515afc Remove unused code in globalizable concern
This code was added in commit 041abe904, but it was never used.
2024-06-05 16:10:56 +02:00
Javi Martín
c11780880c Move form builders to their own folder
We were defining one builder in the `app/lib/` folder and another one
inside a helper module.

So now we're grouping them together. This way we're following the "one
class per file" convention that we follow most of the time. And, by
extracting the `TranslatableFormBuilder` class to its own file, it'll be
easier to add tests for it.

Note that, for consistency, we're renaming the
`TranslationsFieldsBuilder` class so it ends in `FormBuilder`.
2024-06-05 16:10:56 +02:00
Javi Martín
26b48e527a Move information texts form partial to a component
This way it'll be easierto write tests for it when we change it.
2024-06-05 16:10:56 +02:00
Javi Martín
2a5edbb5dd Move content blocks forms partials to components
This way we can simplify the controller a little bit, and it'll be
easier to write tests for them when we change the code.
2024-06-05 16:10:56 +02:00
Javi Martín
1898bdec56 Remove redundant conditions in content blocks controller
In the case of the `edit` action, we're using
`load_and_authorize_resource`, which will always return a
`SiteCustomization::ContentBlock`. In the case of
`edit_heading_content_block`, we're using `Budget::ContentBlock.find`,
which always returns a `Budget::ContentBlock` (or raises an exception).

So, in both cases, the condition to assign `@selected_content_block` can
be removed.
2024-06-05 16:10:56 +02:00
Javi Martín
e7d2cfbf23 Add missing action to content blocks controller
The code works without this action because both the route and the view
are specified, and by default Rails renders the view when there's no
action defined.

However, the code is easier to understand if the action is present in
the controller, which is what we do most of the time.
2024-06-05 16:10:56 +02:00
Javi Martín
709f39c6ce Handle unavailable locales in subscriptions
There was an edge case where a user could configure a locale and then
the application would change the locales to that one would no longer be
available. In that case, we were getting a `I18n::InvalidLocale`
exception when accessing the subscriptions page.

So now, we're defaulting to `I18n.locale`. Note we're using
`I18n.locale`instead of `I18n.default_locale` because `set_user_locale`
is called inside the `switch_locale` block in `ApplicationController`,
which already sets `I18n.locale` based on `I18n.default_locale`.
2024-06-05 16:10:56 +02:00
Javi Martín
3e13f93ebd Add controller tests for switch_locale
This way it'll be easier to change it while checking we haven't broken
existing behavior.

While writing the tests, I noticed we were sometimes storing a symbol in
the session while sometimes we were storing a string. So we're adding a
`to_s` call so we always store a string in the session.
2024-06-05 16:10:56 +02:00
Javi Martín
a2177b4575 Refactor methods to get active locales count
The code is now a bit more readable.
2024-06-05 16:10:56 +02:00
Javi Martín
b188c5cae0 Simplify method to get existing locales
We can get the same results using `pluck`.
2024-06-05 16:10:56 +02:00
Javi Martín
b451a251fc Rename methods that returns an array of locales
Having `_languages` in the method name made it harder to know what that
method was returning.
2024-06-05 16:10:55 +02:00
Javi Martín
5f09718e77 Move globalize locales partial to a component 2024-06-05 16:10:55 +02:00
Javi Martín
9a4a7bd56e Remove obsolete parameter rendering globalize locales
This parameter isn't used since commit b86c0d3c3, which deleted a file
that wasn't used since commit 146c09adb. Further proof that this code
wasn't used is the fact that the `enable_translation_style` method,
which this code called, was removed in commit 5ada97544.
2024-06-05 16:10:55 +02:00
Javi Martín
161eaaff8b Remove redundant condition rendering globalize locales
The `manage_languages` variables is never defined when calling the site
customization information texts globalize locales partial.
2024-06-05 16:10:55 +02:00
Javi Martín
4855fee3d5 Remove unused code in globalize helper
This code isn't used since commit 041abe904.
2024-06-05 16:10:55 +02:00
Javi Martín
cc9794be2a Merge pull request #5569 from consuldemocracy/stats_age_year
Group people by age instead of birth year in stats
2024-06-05 16:10:10 +02:00
Javi Martín
a86f584791 Group people by age instead of birth year in stats
When calculating the stats on, say, January 1st 2024, and using a group
age of, say, between 20 and 24 years, we were considering that everyone
born between 2000 and 2004 had between 20 and 24 years. This wasn't
accurate, since most people born in 2004 would have 19 years at that
point, and most people born in 1999 would have 24 years.
2024-06-04 21:21:02 +02:00
Javi Martín
cca7465221 Fix tests checking age stats in budgets
The age of the participants should refer to the time where the voting
took place, and not the time when the budget was created.

Note that now the tests pass even when the budget is created in a year
but the balloting phase takes place in a different year. By default,
each budget phase lasts for one month, so when we create a budget in a
test, its balloting phase takes place a few months after the date when
the budget is created. Since the `between_ages` scope uses the date of
the balloting phase as a reference, and the test was using the date when
the budget was created as a reference, the test failed depending on
whether those dates took place in the same year or not.
2024-06-04 21:20:14 +02:00
Javi Martín
bb9e47579e Merge pull request #5456 from consuldemocracy/budgets_stats_cache
Don't use the cache in admin budget stats
2024-05-20 16:39:05 +02:00
Julian Herrero
16f844595d Don't use the cache in admin budget stats
In commit e51e03446, we started using the same code to show stats in the
public area and in the admin area. However, in doing so we introduced a
bug, since stats in the public area are only shown after a certain part
of the process has finished, meaning the stats appearing on the page
never change (in theory), so it's perfectly fine to cache them. However,
in the admin area stats can be accessed while the process is still
ongoing, so caching the stats will lead to the wrong results being
displayed.

We've thought about expiring the cache when new supports or ballot lines
are added; however, that means the methods calculating the stats for the
supporting phase would expire when supports are added/removed but the
methods calculating the stats for the voting phase would expire when
ballot lines are added/removed. It gets even more complex because the
`headings` method calculates stats for both the supporting and the
voting phases.

So, since loading stats in the admin section is fast even without the
cache because they only load very basic statistics, we're taking the
simple approach of disabling the cache in this case, so everything works
the same way it did before commit e51e03446.

Co-authored-by: Javi Martín <javim@elretirao.net>
2024-05-20 16:19:41 +02:00
Javi Martín
a4461a1a56 Expire the stats cache once per day
When we first started caching the stats, generating them was a process
that took several minutes, so we never expired the cache.

However, there have been cases where we run into issues where the stats
shown on the screen were outdated. That's why we introduced a task to
manually expire the cache.

But now, generating the stats only takes a few seconds, so we can
automatically expire them every day, remove all the logic needed to
manually expire them, and get rid of most of the issues related to the
cache being outdated.

We're expiring them every day because it's the same day we were doing in
public stats (which we removed in commit 631b48f58), only we're using
`expires_at:` to set the expiration time, in order to simplify the code.

Note that, in the test, we're using `travel_to(time)` so the test passes
even when it starts an instant before midnight. We aren't using
`:with_frozen_time` because, in similar cases (although not in this
case, but I'm not sure whether that's intentional), `travel_to` shows
this error:

> Calling `travel_to` with a block, when we have previously already made
> a call to `travel_to`, can lead to confusing time stubbing.
2024-05-17 20:11:16 +02:00
Javi Martín
fe415d8367 Merge pull request #5542 from consuldemocracy/dependabot/bundler/rexml-3.2.8
Bump rexml from 3.2.6 to 3.2.8
2024-05-17 16:32:28 +02:00
Javi Martín
a5646fcdb3 Remove Cron job to generate stats
Since now generating stats (assuming the results aren't in the cache)
only takes a few seconds even when there are a hundred thousand
participants, as opposed to the several minutes it took to generate them
when we introduced the Cron job, we can simply generate the stats during
the first request to the stats page.

Note that, in order to avoid creating a temporary table when the stats
are cached, we're making sure we only create this table when we need to.
Otherwise, we could spend up to 1 second on every request to the stats
page creating a table that isn't going to be used.

Also note we're using an instance variable to check whether we're
creating a table; I tried to use `table_exists?`, but it didn't work. I
wonder whether `table_exists?` doesn't detect temporary tables.
2024-05-17 16:08:08 +02:00
Javi Martín
653848fc4e Extract method to get the stats key in stats
This way we remove a bit of duplication and it'll be easier to change
the `stats_cache` method.
2024-05-17 16:08:08 +02:00
Julian Herrero
f30fa29994 Allow running specs scenarios using the Rails cache
Cache is by default disabled on every non-production environment
2024-05-17 16:08:08 +02:00
Javi Martín
62cd4c8d7b Add indices to stats temporary tables
Since we're doing many queries to get stats for each age group and each
geozone, testing shows these indices make stats calculation about 25%
faster on processes with 100,000 participants.
2024-05-17 16:08:08 +02:00
Javi Martín
80dcbfc23c Improve performance generating stats
Debugging shows that the bottleneck in the stats calculation is the
number of times we're querying the users table using the same array of
IDs in the `where` condition but each time combined with other
conditions.

So we're inserting the results of querying the users table with the
array of IDs in a temporary table and using this temporary table for the
other calculations. When querying this temporary table, there's no need
to filter for IDs anymore.

For budget stats, the `generate` method is now about 10-20 times faster
for a budget with 20,000 participants. For budgets with only a few dozen
participants, there's no significant difference in performance.

I thought about modifying the `participants` method and use the
temporary table there. The problem, however, is that in this case it
isn't clear when to drop the temporary table, and we could end up with
thousands of temporary tables in the database if we don't do it right.
Creating and dropping the temporary table in the same transaction, on
the other hand, guarantees that won't be the case.

Note there's no risk of duplicate tables since they're created and
dropped inside a transaction, so we're always using the same table name
for the same resource. We're adding a test that fails with a
`PG::DuplicateTable: ERROR:  relation "participants__1"` error if we
don't use a transaction.
2024-05-17 16:08:04 +02:00
Javi Martín
6f0c27c0fb Remove unused code in statisticable concern
This code isn't used since commit e3063cd24f.
2024-05-17 16:07:26 +02:00
Javi Martín
bcc9fd97f5 Revert "Extract class to manage GeozoneStats"
Back in commit 383909e16, we said:

> 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.

Since there haven't been any changes in the last 5 years and we've found
cases where using the GeozoneStats class results in a slightly worse
performance, we're removing this class. The code is now a bit easier to
read, and is consistent with the way we calculate participants by age.

This reverts commit 383909e16.
2024-05-17 16:07:26 +02:00
Javi Martín
6eadf3cea6 Merge pull request #5533 from consuldemocracy/age_stats
Calculate age stats based on the participation date
2024-05-17 16:05:59 +02:00
Javi Martín
c92cb6bed1 Use a range in the between_ages user scope
Using `>` and `<` for dates is a bit confusing because it usually
requires mentally translating `<` into "before" and `>` into "after",
meaning it takes a few seconds to check which is the starting date and
which is the ending date. When using a range, it's easier to see which
is which.

Note that we were using `>` and `<`, but now we're using the equivalent
of `>=` and `<=`. This makes sense IMHO, since the beginning of the year
and the end of the year should also be included in this case.
2024-05-17 15:36:52 +02:00
dependabot[bot]
05ca4cd1a2 Bump rexml from 3.2.6 to 3.2.8
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.6 to 3.2.8.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-16 21:43:32 +00:00
Javi Martín
98ca8e19f6 Merge pull request #5538 from consuldemocracy/dependabot/bundler/nokogiri-1.16.5
Bump nokogiri from 1.16.3 to 1.16.5
2024-05-15 19:00:48 +02:00
dependabot[bot]
613a9471d6 Bump nokogiri from 1.16.3 to 1.16.5
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.3 to 1.16.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.3...v1.16.5)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-15 16:46:08 +00:00
Javi Martín
1d85a63e7c Calculate age stats based on the participation date
We were calculating the age stats based on the age of the users who
participated... at the moment where we were calculating the stats. That
means that, if 20 years ago, 1000 people who were 16 years old
participated, they would be shown as having 36 years in the stats.

Instead, we want to show the stats at the time when the process took
place, so we're implementing a `participation_date` method.

Note that, for polls, we could actually use the `age` column in the
`poll_voters` table. However, doing so would be harder, would only work
for polls but not for budgets, and it wouldn't be statistically very
relevant, since the stats are shown by age groups, and only a small
percentage of people would change their age group (and only to the
nearest one) between the time they participate and the time the process
ends.

We might use the `poll_voters` table in the future, though, since we
have a similar issue with geozones and genders, and using the
information in `poll_voters` would solve it as well (only for polls,
though).

Also note that we're using the `ends_at` dates because some people but
be too young to vote when a process starts but old enough to vote when
the process ends.

Finally, note that we might need to change the way we calculate the
participation date for a budget, since some budgets might not enabled
every phase. Not sure how stats work in that scenario (even before these
changes).
2024-05-13 15:42:37 +02:00
Javi Martín
947953641d Add test for between_ages user scope
We were already testing it as part of the statisticable concern, but
it's easier to understand when we have specific tests for it as well.

Note that we're using `eq` insteab of `be` because it's what we use most
often in the test. For consistency, we're also changing the tesst in
budget stats so it uses `eq`.
2024-05-13 15:42:37 +02:00