Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
27 changes: 0 additions & 27 deletions .github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ GIT

GIT
remote: https://github.com/OpenSourcePolitics/decidim-module-anonymous_proposals.git
revision: 2d9dc00745ca1cc52b4cb73c3177dcd77e36c031
revision: e74c279204b74bbac3e394ba589b40a2825eca6e
branch: bump/0.29
specs:
decidim-anonymous_proposals (0.29.3)
Expand Down Expand Up @@ -64,7 +64,7 @@ GIT

GIT
remote: https://github.com/OpenSourcePolitics/decidim-module-extra_user_fields.git
revision: 9b4e8d153342fed90cbd06675f91e74ee831b81b
revision: d456beeee5d4a7932160618b31b064715a955c73
branch: bump/0.29
specs:
decidim-extra_user_fields (0.29.0)
Expand Down Expand Up @@ -94,7 +94,7 @@ GIT

GIT
remote: https://github.com/OpenSourcePolitics/decidim-module-term_customizer.git
revision: f3b55cae4e22713d7c842f0de4c421e9765ad7b0
revision: 08a7a55ac5336a5d3e2d549441423ca961714dce
branch: backport/fix_database_not_available
specs:
decidim-term_customizer (0.29.0)
Expand Down Expand Up @@ -122,10 +122,10 @@ GIT

GIT
remote: https://github.com/decidim-ice/decidim-module-decidim_awesome.git
revision: f2c71529cfb362d2144f8728de9c98cf92f27342
revision: a9f2077f80438f6bc7b9fa7c26aa150cac46523a
branch: release/0.29-stable
specs:
decidim-decidim_awesome (0.12.5)
decidim-decidim_awesome (0.12.6)
active_hashcash (~> 0.4.0)
decidim-admin (>= 0.29.1, < 0.30)
decidim-core (>= 0.29.1, < 0.30)
Expand Down
87 changes: 87 additions & 0 deletions app/jobs/decidim/find_and_update_descendants_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

module Decidim
class FindAndUpdateDescendantsJob < ApplicationJob
queue_as :default

BATCH_SIZE = 100

def perform(element)
process_element_and_descendants(element)
end

private

def process_element_and_descendants(element)
reindex_element(element)
process_components(element)
process_comments(element)
end

def reindex_element(element)
return unless element.class.respond_to?(:searchable_resource?) && element.class.searchable_resource?(element)

org = element.class.search_resource_fields_mapper.retrieve_organization(element)
return unless org

searchables_in_org = element.searchable_resources.by_organization(org.id)
should_index = element.class.search_resource_fields_mapper.index_on_update?(element)

if should_index
if searchables_in_org.empty?
element.add_to_index_as_search_resource
else
fields = element.class.search_resource_fields_mapper.mapped(element)
searchables_in_org.find_each do |sr|
next if sr.blank?

attrs = element.send(:contents_to_searchable_resource_attributes, fields, sr.locale)
# rubocop:disable Rails/SkipsModelValidations
sr.update_columns(attrs)
# rubocop:enable Rails/SkipsModelValidations
end
end
elsif searchables_in_org.any?
searchables_in_org.delete_all
end
end

def process_components(element)
return unless element.respond_to?(:components) && element.components.any?

element.components.find_each do |component|
klass = component_class(component)
next unless valid_component_class?(klass)

klass.where(component:).find_in_batches(batch_size: BATCH_SIZE) do |batch|
batch.each { |descendant| process_element_and_descendants(descendant) }
end
end
end

def process_comments(element)
return unless element.respond_to?(:comments) && element.comments.any?

element.comments.find_in_batches(batch_size: BATCH_SIZE) do |batch|
batch.each { |comment| process_element_and_descendants(comment) }
end
end

def component_class(component)
return Decidim::Blogs::Post if component.manifest_name == "blogs"

manifest_name_to_class(component.manifest_name)
end

def manifest_name_to_class(name)
resource_registry = Decidim.resource_registry.find(name)
return if resource_registry.blank?

resource_registry.model_class_name&.safe_constantize
end

def valid_component_class?(klass)
klass.present? && klass.column_names.include?("decidim_component_id")
end
end
end
3 changes: 2 additions & 1 deletion app/packs/entrypoints/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@

import "src/decidim/admin/reorder_scopes";
import "src/decidim/admin/reorder_proposal_states";
import "src/decidim/surveys/sorted_answers_fixes";
import "src/decidim/surveys/sorted_answers_fixes";
import "src/decidim/check_boxes_tree";
130 changes: 130 additions & 0 deletions app/packs/src/decidim/check_boxes_tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* CheckBoxesTree component.
*/
export default class CheckBoxesTree {
constructor() {
this.checkboxesTree = Array.from(document.querySelectorAll("[data-checkboxes-tree]"));

if (!this.checkboxesTree.length) {
return;
}

this.checkboxesLeaf = Array.from(document.querySelectorAll("[data-children-checkbox] input"));

// handles the click in a tree, what means to mark/unmark every children
this.checkboxesTree.forEach((input) => input.addEventListener("click", (event) => this.checkTheCheckBoxes(event.target)));
// handles the click in a leaf, what means to update the parent possibly
this.checkboxesLeaf.forEach((input) => input.addEventListener("change", (event) => this.checkTheCheckParent(event.target)));
// Review parent checkboxes on initial load
this.checkboxesLeaf.forEach((input) => this.checkTheCheckParent(input));
}

/**
* Set checkboxes as checked if included in given values
* @public
* @param {Array} checkboxes - array of checkboxes to check
* @param {Array} values - values of checkboxes that should be checked
* @returns {Void} - Returns nothing.
*/
updateChecked(checkboxes, values) {
checkboxes.each((_idx, checkbox) => {
if ((checkbox.value === "" && values.length === 1) || (checkbox.value !== "" && values.includes(checkbox.value))) {
checkbox.checked = true;
this.checkTheCheckBoxes(checkbox);
this.checkTheCheckParent(checkbox);
}
});
}

/**
* Set the container form(s) for the component, to disable ignored filters before submitting them
* @public
* @param {query} theForm - form or forms where the component will be used
* @returns {Void} - Returns nothing.
*/
setContainerForm(theForm) {
theForm.on("submit ajax:before", () => {
theForm.find(".ignore-filters input, input.ignore-filter").each((_idx, elem) => {
elem.disabled = true;
});
});

theForm.on("ajax:send", () => {
theForm.find(".ignore-filters input, input.ignore-filter").each((_idx, elem) => {
elem.disabled = false;
});
});
}

/**
* Handles the click action on any checkbox.
* @private
* @param {Input} target - the input that has been checked
* @returns {Void} - Returns nothing.
*/
checkTheCheckBoxes(target) {
const targetChecks = target.dataset.checkboxesTree;
const checkStatus = target.checked;

// NOTE: Note the regex CSS query, it selects those [data-children-checkbox] ended with the target id
const allChecks = document.querySelectorAll(`[data-children-checkbox$="${targetChecks}"] input`);

allChecks.forEach((input) => {
input.checked = checkStatus;
input.indeterminate = false;
input.classList.add("ignore-filter");

// recursive call if the input it is also a tree
if (input.dataset.checkboxesTree) {
this.checkTheCheckBoxes(input)
}
});
}

/**
* Update children checkboxes state when the current selection changes
* @private
* @param {Input} input - the checkbox to check its parent
* @returns {Void} - Returns nothing.
*/
checkTheCheckParent(input) {
const key = input.parentNode.dataset.childrenCheckbox
// search in the checkboxes array if some id ends with the childrenCheckbox key, what means it is the parent
const parentCheck = this.checkboxesTree.find(({ id }) => new RegExp(`${key}$`, "i").test(id))
if (typeof parentCheck === "undefined") {
return;
}

// search for leaves with the same parent, what means they are siblings
const totalCheckSiblings = this.checkboxesLeaf.filter((node) => node.parentNode.dataset.childrenCheckbox === key)
const checkedSiblings = totalCheckSiblings.filter((checkbox) => checkbox.checked)
const indeterminateSiblings = totalCheckSiblings.filter((checkbox) => checkbox.indeterminate)

if (checkedSiblings.length === 0 && indeterminateSiblings.length === 0) {
parentCheck.checked = false;
parentCheck.indeterminate = false;
} else if (totalCheckSiblings.length === 1 && checkedSiblings.length === totalCheckSiblings.length && indeterminateSiblings.length === 0){
parentCheck.checked = false;
parentCheck.indeterminate = true;
} else if (checkedSiblings.length === totalCheckSiblings.length && indeterminateSiblings.length === 0) {
parentCheck.checked = true;
parentCheck.indeterminate = false;
} else {
parentCheck.checked = false;
parentCheck.indeterminate = true;
}

totalCheckSiblings.forEach((sibling) => {
if (parentCheck.indeterminate && !sibling.indeterminate) {
sibling.classList.remove("ignore-filter");
} else {
sibling.classList.add("ignore-filter");
}
});

// recursive call if there are more children
if ("childrenCheckbox" in parentCheck.parentNode.dataset) {
this.checkTheCheckParent(parentCheck);
}
}
}
5 changes: 5 additions & 0 deletions lib/extends/permissions/initiatives/permissions_extends.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ module PermissionsExtends
def creation_enabled?
return false unless Decidim::Initiatives.creation_enabled
return true if no_authorizations_available?
return true if no_create_permission_on_initiative_type?

user_can_create? && authorized?(:create, permissions_holder: initiative_type)
end

private

def no_create_permission_on_initiative_type?
initiative_type.permissions.nil? || initiative_type.permissions.keys.empty? || !initiative_type.permissions&.keys&.include?("create")
end

def no_authorizations_available?
user&.organization&.available_authorizations&.empty?
end
Expand Down
35 changes: 35 additions & 0 deletions spec/jobs/find_and_update_descendants_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "spec_helper"

module Decidim
describe FindAndUpdateDescendantsJob do
subject { described_class }

let!(:participatory_process) { create(:participatory_process) }
let!(:proposal_component) { create(:proposal_component, participatory_space: participatory_process) }
let!(:proposal) { create(:proposal, :official, component: proposal_component) }

describe "queue" do
it "is queued to events" do
expect(subject.queue_name).to eq "default"
end
end

describe "#perform" do
it "enqueues a job with perform_later" do
expect do
Decidim::FindAndUpdateDescendantsJob.perform_later(participatory_process)
end.to have_enqueued_job(Decidim::FindAndUpdateDescendantsJob).with(participatory_process)
end

it "calls process_element_and_descendants private method" do
job = described_class.new
allow(job).to receive(:process_element_and_descendants)

job.perform(participatory_process)
expect(job).to have_received(:process_element_and_descendants).with(participatory_process)
end
end
end
end
Loading
Loading