Skip to content

Add taxonomy filters for components and resources#13296

Merged
alecslupu merged 208 commits intodevelopfrom
feature/taxonomies-filters-for-components
Oct 30, 2024
Merged

Add taxonomy filters for components and resources#13296
alecslupu merged 208 commits intodevelopfrom
feature/taxonomies-filters-for-components

Conversation

@microstudi
Copy link
Copy Markdown
Contributor

@microstudi microstudi commented Aug 21, 2024

🎩 What? Why?

This is a big PR that covers the substitution of the current, participatory spaces dependent, categories and (global) scopes and areas for a generic tree of items called taxonomies. Taxonomies are not meant to be used directly by different participatory spaces or components but instead, using an intermediate filter that allows to choose specifically which taxonomies are used in each place.

Architecture:
taxonomies drawio

  1. Taxonomies table: Can have a parent or not. Taxonomies without a parent are "root taxonomies", they cannot be linked to any resource directly.
  2. Taxonomy filters: This filters are defined for each participatory space type (for all of them). They are linked to a root category, any number of filters can be linked to a single root taxonomy. Each filter, has many "filter items".
  3. Taxonomy filter item: This is used to allow filtering the final taxonomies that are shown in the web frontend. This way, certain component can show some parts of taxonomies and another other, while all of them are linked to the same taxonomy tables (this will allow a global search for resources in the future).

Creation of a filter for a type of participatory space:
imatge

List of filters for a type of participatory space:
imatge

Usage of filters in participatory spaces:
After creating some filters for type of participatory space (for instance, an Assembly), all these filter will be available in the participatory space configuration (info):
imatge

In the public web side, filters are used instead of the old scope/areas search:
imatge

Usage of filters in components
Filters can also be assigned in the component's configuration page:
imatge

Usage of these filters depend on the component intention but, in general, are used to allow users and admins to assign one of the taxonomies that the filter provides (all other taxonomies will be hidden).
imatge

Also used in the frontend for filtering:
imatge

Or in the admin side:
imatge

Note that, in both cases, resources are linked to taxonomies, never to filters. So removing a filter does not breaks any association between an existing resource an a taxonomy.

Technical implementation

DATABASE:

  1. Root taxonomies and taxonomies are the same table. A "root taxonomy" is simply the first level (no parents). A resource can be associated to a taxonomy through the "taxonomizations" table. Validations are in place to prevent associate a resource to a root taxonomy. Currently, there are no validations to prevent destroy a taxonomy if it has taxonomizations (this might change in the future, but this is by design at this point).
module Decidim
  class Taxonomy < ApplicationRecord
    before_validation :update_part_of, on: :update
    after_create_commit :create_part_of
    belongs_to :organization, inverse_of: :taxonomies
    belongs_to :parent, inverse_of: :children, optional: true
    has_many :children, inverse_of: :parent, dependent: :destroy
    has_many :taxonomizations, dependent: :destroy
    has_many :taxonomy_filters, dependent: :destroy
    has_many :taxonomy_filter_items, dependent: :destroy

    validates :name, presence: true
    validate :validate_max_children_levels
    validate :forbid_cycles
...
module Decidim
  class Taxonomization < ApplicationRecord
    belongs_to :taxonomy, inverse_of: :taxonomizations
    belongs_to :taxonomizable, polymorphic: true

    validate :prevent_root_taxonomization
...
  1. Filters are associated to a root taxonomy and have many taxonomy_filter_items, which in turn are associated to a specific taxonomy. The final tree of taxonomies is built in the frontend using the specified items only.
    NOTE: An alternative to this design could've been just a JSONB column in the filters table storing all the category ids for that filter. I decided to go with the traditional approach to favor integrity and cascade destroys.
module Decidim
  class TaxonomyFilter < ApplicationRecord
    belongs_to :root_taxonomy, inverse_of: :taxonomy_filters
    has_many :filter_items, inverse_of: :taxonomy_filter, dependent: :destroy

    validate :root_taxonomy_is_root
    validate :space_manifest_is_registered

    delegate :name, to: :root_taxonomy
module Decidim
  class TaxonomyFilterItem < ApplicationRecord
    belongs_to :taxonomy_filter, inverse_of: :filter_items
    belongs_to :taxonomy_item, inverse_of: :taxonomy_filter_items

    validate :taxonomy_item_is_not_root
    validate :taxonomy_item_is_child_of_taxonomy_filter_root

Note that filters use the same name as the parent taxonomy, in the future we might want to add a column to change the name of the filter so it can be distinct if several filters are associated to the same root taxonomy.

REUSABLE PARTS:

  1. The taxonomy_filters Settings type (defined in files decidim-admin/app/helpers/decidim/admin/settings_helper.rb and decidim-core/lib/decidim/settings_manifest.rb). It is a new type of form field for components to allow specify the ids of the taxonomy filters that going to be used in that component. Uses a lateral drawer to choose from the available ones. This is used by specifying something like this in the components definition:
# decidim-debates/lib/decidim/debates/component.rb
Decidim.register_component(:debates) do |component|
  component.engine = Decidim::Debates::Engine
 ...
component.settings(:global) do |settings|
  settings.attribute :taxonomy_filters, type: :taxonomy_filters
  1. The concern decidim-core/lib/decidim/has_taxonomy_settings.rb: Provides with methods to extract available taxonomies and filters according to the components settings configuration. This are intended to be used in parts where the user has to assign taxonomies to a resource. Added to the Component model.
  2. The concern decidim-admin/app/forms/concerns/decidim/has_taxonomy_form_attributes.rb: Provides with attribute definitions for adding multiple taxonomies in a Decidim::Form object. It only requires for the implementing class to return the manifest of the participatory space (which is used to obtain the list of filters available). Used in every resource form that needs to include taxonomies (ie: AssemblyForm, ProposalForm, etc...).
    A related file is decidim-core/app/helpers/decidim/taxonomies_helper.rb which defines the method taxonomy_items_options_for_filter that is used in the form views as:
    <% if @form.taxonomy_filters&.any? %>
      <% @form.taxonomy_filters.each do |filter| %>
        <div class="row column">
          <%= filter_taxonomy_items_select_field form, :taxonomies, filter %>
        </div>
      <% end %>
    <% end %>
  1. The concern decidim-admin/app/controllers/concerns/decidim/admin/filterable.rb: Provides with methods to obtain taxonomies and defines ransack attributes for searching resources using taxonomies. Added in every controller that need to deal with searching by taxonomy (public or admin side).
    Related to using filters, the decidim-core/app/helpers/decidim/check_boxes_tree_helper.rb and other related files have been modified to allow sending matrix-like id definitions. This is because, now in the sidebar, we list every root taxonomy (enabled by a filter) as it was a main item (for instance in a proposal index page you can search by author, state and every taxonomy as it was an independent filter). To handle this in ransack, the scope with_any_taxonomies have been defined that can deal with a tree of taxonomies (see the decidim-core/lib/decidim/taxonomizable.rb concern). Now, to show the taxonomies in the sidebar usually you just need it to add to the filter_sections method like this:
def filter_sections
      items = []
      ...
      available_taxonomy_filters.find_each do |taxonomy_filter|
        items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
                     collection: filter_taxonomy_values_for(taxonomy_filter),
                     label: decidim_sanitize_translated(taxonomy_filter.name),
                     id: "taxonomy")
      end
      ...
      items
    end

    # in case of a resource this is just `current_component.available_taxonomy_filters` provided by `HasTaxonomySettings`
    def available_taxonomy_filters
      Decidim::TaxonomyFilter.for(:assemblies)
    end

This applies to all components currently using categories or scopes.

In addition theses component have further changes as they where using categories in other ways:

  1. The Accountability component: This component uses categories to generate a 2 level tree and display in each of them the projects assigned to that category. This has been changed to use taxonomies allowed by the taxonomy filter enabled in settings. Note that editing/removing a filter from the settings will effectively hide all the related projects from the public web frontend.

  2. The Sortitions component: Sortitions used categories to filter the proposals to be randomized. This has been change also to use the available taxonomies allowed by filters. Past sortitions are not affected by changes in the filters/taxonomies.

Additional changes included in this PR:

  • Adds Graphql types for taxonomies in resources and organization
  • Modifies the tags_cell to use taxonomies instead of categories/scopes
  • Adds filter definition to initiatives (so components can work) but it does not touch the currently behaviour of initiative types that is linked to scopes.
  • Removed some unused views and classes.
  • Sorting and filtering in the admin by taxonomies.

Further work for future PRs

  • Add seeds with taxonomies
  • Handle imports/exports with categories
  • Remove scopes from settings and participatory space forms.
  • Some bulk actions to handle changing categories in resources (proposals, meetings...)
  • Refactor initiatives to avoid using scopes
  • Allow GraphQL to filter collection using taxonomies
  • Create an importer to convert categories/areas/scopes/participatory space types to taxonomies
  • Handle user interests with taxonomies instead of scopes
  • Filter newsletter users based on taxonomies/user interests.
  • Add documentation (both technical and functional)

📌 Related Issues

Link your PR to an issue

Testing

  1. Define global taxonomies
  2. Define filters in a participatory space
  3. Use those filters to assign taxonomies into a participatory space
  4. Use those filters to assign available filters into a component
  5. Use the available taxonomies to create resources according to the setting definition.

📷 Screenshots

Edit component filters:
Screencast from 22-08-24 17:40:56.webm

Tasks:

  • Create the setting attribute taxonomy_filters
  • Add it to proposal components
  • Substitute the category/scope select in proposals by this filter
  • Conference filters
  • Initiative filters. Intiatives is linked to scopes and areas in slightly different ways that Processes, assemblies and conferences. Changing this to use taxonomies should be tackled in a later PR
  • Proposals
  • Proposal graphql type
  • Blog
  • Budgets
  • Budget graphql type
  • Debates
  • Debate GraphQl type
  • Page
  • Meetings
  • Meeting graphql type
  • Directory meetings
  • Sortitions
  • Sortition GraphQL type
  • Survey
  • Accountability is linked to categories (change to this?). Needs clarification, might be out of scope
  • Accountability graphql type
  • Substitute the actual filter in proposals (check other places) for the filters established in the component setting.
  • Graphql: taxonomy input filter This would be great to filter in the API by taxonomies all the Taxonomizable elements.
    When doing that, the current query_extensions could be improved by unlinking from the core the participatory process filter.
  • Replace areas/scopes search for participatory spaces
    • Processes
    • Process groups
    • Assemblies
    • Conferences
    • Inititatives
  • Handle metrics calculation
  • Handle imports with categories/scopes
  • Handle components serializers (add taxonomies)
  • Handler participatory space serializers (add taxonomies)
  • All specs

♥️ Thank you!

github-actions[bot]
github-actions bot previously approved these changes Oct 29, 2024
@microstudi
Copy link
Copy Markdown
Contributor Author

the followup PR is this: #13584

github-actions[bot]
github-actions bot previously approved these changes Oct 29, 2024
github-actions[bot]
github-actions bot previously approved these changes Oct 29, 2024
github-actions[bot]
github-actions bot previously approved these changes Oct 29, 2024
github-actions[bot]
github-actions bot previously approved these changes Oct 30, 2024
github-actions[bot]
github-actions bot previously approved these changes Oct 30, 2024
github-actions[bot]
github-actions bot previously approved these changes Oct 30, 2024
Copy link
Copy Markdown
Contributor

@alecslupu alecslupu left a comment

Choose a reason for hiding this comment

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

on tiny change

Co-authored-by: Alexandru Emil Lupu <contact@alecslupu.ro>
Copy link
Copy Markdown
Contributor

@alecslupu alecslupu left a comment

Choose a reason for hiding this comment

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

great work!

@alecslupu
Copy link
Copy Markdown
Contributor

We whould try to avoid this kind of PR in the future.

image

It makes it hard ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

No open projects
Archived in project

Development

Successfully merging this pull request may close these issues.

Add taxonomy filters to components

6 participants