From ae55db07507d223e4619c71dad58dfa1bb3e527d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Tue, 21 Feb 2023 11:07:51 +0100 Subject: [PATCH 01/13] Admin accountability (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New feature: customizable etiquette rules (#1) * Add configuration options * erb lint * set validations * fix summary test * fixes specs * fix config merger * bump version. add docs * Setup admin accountablity (#6) * add new section, add new controller, add view index * add permissions to index action * add rspec tests * add var to config and rspec * fix initializer * fix lint * fix permissions * refactor permissions handling * fix test Co-authored-by: Ivan Vergés * View and controllers (#7) * improve the controller and add a table to the view * logs list * add PaperTrail instead ActionLog * add removal date to table * change datetime format * move remove instance var from helper * add styles to table, add role * add pagination * fix pagination * add rspec * change controller * change controller * fix lint * add rspec * add participatory_space_type to table * fix lint * refactoring * change method's name * improve the controller and add a table to the view * logs list * add PaperTrail instead ActionLog * add removal date to table * change datetime format * move remove instance var from helper * add styles to table, add role * add pagination * fix pagination * add rspec * change controller * change controller * fix lint * add rspec * add participatory_space_type to table * fix lint * refactoring * change method's name * add config for types_user_roles * change view and helper, move building html to view from helper * fix lint * fix lint * add presenter, remove helper * fix pagination * add link to participatory space, add presenter spec * extract the i18n version of the role * add rspec PaperTrailVersion * rspec PaperTrailRolePresenter * Fix presenter * fix tests * fix presenter * handle deleted users * fix checksums --------- Co-authored-by: Ivan Vergés * Search and filtering (#8) * add Filterable * fix searching and filtering * add filter by date * add rspec * change ransacker method name * change locale * fix test * fix filter by date examples --------- Co-authored-by: Ivan Vergés * Export excel/csv (#9) * add export of admin_actions * restore Gemfile.lock * update gemfile * add reference decidim admin for locales * commit used Decidim styles * restore envs * remove default * add tests --------- Co-authored-by: Ivan Vergés * configurable roles from initializer (#10) * translate values * make roles configurable * fix tests * Show superadmins in admin accountability (#12) * refactor initializer & presenter clasess * complete space role test * differentiate admins from space admins * add user presenter specs * fix filters * add tests for super admins * fix tests * fix permissions * Add readme instructions * update version * fix readme --------- Co-authored-by: Anna Topalidi <60363870+antopalidi@users.noreply.github.com> --- .eslintrc.json | 248 +- .github/workflows/tests-legacy.yml | 2 + .github/workflows/tests.yml | 2 + CHANGELOG.md | 11 + Gemfile | 2 +- Gemfile.legacy.lock | 2 +- Gemfile.lock | 35 +- README.md | 8 + .../admin_accountability/admin/filterable.rb | 67 + .../admin/filterable_helper.rb | 37 + .../admin/admin_accountability_controller.rb | 51 + .../export_admin_actions_job.rb | 28 + .../decidim_awesome/paper_trail_version.rb | 99 + .../decidim_admin_decidim_awesome.js | 5 +- .../decidim_awesome/admin/auto_edit.js | 14 +- .../admin/check_redirections.js | 4 +- .../decidim_awesome/admin/constraints.js | 10 +- .../admin/custom_fields_builder.js | 21 +- .../decidim_awesome/admin/form_exit_warn.js | 1 + .../decidim_awesome/admin/user_picker.js | 1 + .../awesome_map/api/fetcher.js | 26 +- .../awesome_map/awesome_map.js | 26 +- .../awesome_map/controllers/controller.js | 19 +- .../controllers/proposals_controller.js | 6 +- .../awesome_map/controls_ui.js | 51 +- .../decidim_awesome/awesome_map/load_map.js | 1 + .../decidim/decidim_awesome/editors/editor.js | 14 +- .../decidim/decidim_awesome/forms/autosave.js | 20 +- .../forms/custom_fields_renderer.js | 63 +- .../decidim_awesome/forms/rich_text_plugin.js | 10 +- .../proposals/custom_fields.js | 14 +- .../decidim_awesome/proposals/images.js | 4 +- .../decidim_awesome/admin/permissions.rb | 16 +- .../paper_trail_base_presenter.rb | 28 + .../participatory_space_role_presenter.rb | 45 + .../decidim_awesome/role_base_presenter.rb | 102 + .../decidim_awesome/user_entity_presenter.rb | 42 + .../paper_trail_version_serializer.rb | 37 + .../admin/admin_accountability/index.html.erb | 59 + .../admin/shared/_filters_with_date.html.erb | 56 + babel.config.json | 28 + config/i18n-tasks.yml | 1 + config/locales/en.yml | 56 + examples/admin_accountability.png | Bin 0 -> 444211 bytes lib/decidim/decidim_awesome/admin_engine.rb | 16 +- lib/decidim/decidim_awesome/awesome.rb | 15 + lib/decidim/decidim_awesome/test/factories.rb | 7 + .../action_log_presenter_examples.rb | 61 + lib/decidim/decidim_awesome/version.rb | 2 +- package-lock.json | 12152 +++++++++++----- package.json | 177 +- .../admin_accountability_controller_spec.rb | 78 + spec/jobs/export_admin_actions_job_spec.rb | 144 + spec/models/paper_trail_version_spec.rb | 46 + spec/permissions/admin/permissions_spec.rb | 17 + ...participatory_space_role_presenter_spec.rb | 154 + spec/presenters/role_base_presenter_spec.rb | 117 + spec/presenters/user_entity_presenter_spec.rb | 104 + ...min_accountability_admin_filtering_spec.rb | 190 + .../admin_accountability_ps_filtering_spec.rb | 221 + .../system/admin/admin_accountability_spec.rb | 186 + 61 files changed, 10968 insertions(+), 4091 deletions(-) create mode 100644 app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable.rb create mode 100644 app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb create mode 100644 app/controllers/decidim/decidim_awesome/admin/admin_accountability_controller.rb create mode 100644 app/jobs/decidim/decidim_awesome/export_admin_actions_job.rb create mode 100644 app/models/decidim/decidim_awesome/paper_trail_version.rb create mode 100644 app/presenters/decidim/decidim_awesome/paper_trail_base_presenter.rb create mode 100644 app/presenters/decidim/decidim_awesome/participatory_space_role_presenter.rb create mode 100644 app/presenters/decidim/decidim_awesome/role_base_presenter.rb create mode 100644 app/presenters/decidim/decidim_awesome/user_entity_presenter.rb create mode 100644 app/serializers/decidim/decidim_awesome/paper_trail_version_serializer.rb create mode 100644 app/views/decidim/decidim_awesome/admin/admin_accountability/index.html.erb create mode 100644 app/views/decidim/decidim_awesome/admin/shared/_filters_with_date.html.erb create mode 100644 babel.config.json create mode 100644 examples/admin_accountability.png create mode 100644 lib/decidim/decidim_awesome/test/shared_examples/action_log_presenter_examples.rb create mode 100644 spec/controllers/admin/admin_accountability_controller_spec.rb create mode 100644 spec/jobs/export_admin_actions_job_spec.rb create mode 100644 spec/models/paper_trail_version_spec.rb create mode 100644 spec/presenters/participatory_space_role_presenter_spec.rb create mode 100644 spec/presenters/role_base_presenter_spec.rb create mode 100644 spec/presenters/user_entity_presenter_spec.rb create mode 100644 spec/system/admin/admin_accountability_admin_filtering_spec.rb create mode 100644 spec/system/admin/admin_accountability_ps_filtering_spec.rb create mode 100644 spec/system/admin/admin_accountability_spec.rb diff --git a/.eslintrc.json b/.eslintrc.json index 8b788aade..88007db2f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,247 +1,3 @@ { - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "eslint:recommended", - "prettier" - ], - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "globals": { - "require": false, - "$": false, - "jQuery": false, - "L": false, - "ApiFetcher": false, - "FormStorage": false, - "Quill": false, - "InscrybMDE": false, - "CodeMirror": false, - "Europa": false, - "DecidimAwesome": false, - "inlineAttachment": false, - "CustomFieldsBuilder": false - }, - "rules": { - "accessor-pairs": "error", - "array-bracket-spacing": "error", - "array-callback-return": "error", - "arrow-body-style": "off", - "arrow-parens": [ - "error", - "always" - ], - "arrow-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - "block-scoped-var": "error", - "block-spacing": "off", - "callback-return": "error", - "class-methods-use-this": "off", - "comma-dangle": ["error", "never"], - "comma-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "comma-style": [ - "error", - "last" - ], - "complexity": "error", - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-this": "error", - "curly": "error", - "default-case": "error", - "dot-location": "error", - "dot-notation": "error", - "eol-last": "error", - "func-call-spacing": "error", - "func-name-matching": "error", - "func-names": [ - "error", - "never" - ], - "generator-star-spacing": "error", - "global-require": "error", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-match": "error", - "indent": ["error", 2, { "VariableDeclarator": 2 }], - "jsx-quotes": "error", - "key-spacing": "error", - "keyword-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "error", - "lines-around-directive": "error", - "max-len": "off", - "max-lines": "error", - "max-nested-callbacks": "error", - "max-params": "error", - "max-statements": "off", - "max-statements-per-line": "error", - "multiline-ternary": "error", - "new-cap": "error", - "new-parens": "error", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": "off", - "no-alert": "error", - "no-array-constructor": "error", - "no-bitwise": "error", - "no-caller": "error", - "no-catch-shadow": "error", - "no-confusing-arrow": "error", - "no-continue": "error", - "no-div-regex": "error", - "no-duplicate-imports": "error", - "no-else-return": "error", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-coercion": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-magic-numbers": "off", - "no-mixed-requires": "error", - "no-multi-spaces": "off", - "no-multi-str": "error", - "no-multiple-empty-lines": "error", - "no-native-reassign": "error", - "no-negated-condition": "error", - "no-negated-in-lhs": "error", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-path-concat": "error", - "no-plusplus": "error", - "no-process-env": "error", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "error", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sync": "error", - "no-tabs": "error", - "no-template-curly-in-string": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "off", - "no-undef-init": "error", - "no-undefined": "error", - "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-unused-expressions": "error", - "no-use-before-define": "error", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-escape": "error", - "no-useless-rename": "error", - "no-var": "error", - "no-void": "error", - "no-warning-comments": "error", - "no-whitespace-before-property": "error", - "no-with": "error", - "object-curly-newline": "off", - "object-curly-spacing": "off", - "object-property-newline": [ - "error", - { - "allowMultiplePropertiesPerLine": true - } - ], - "object-shorthand": "off", - "one-var": "off", - "one-var-declaration-per-line": "error", - "operator-assignment": "error", - "operator-linebreak": "error", - "padded-blocks": "off", - "prefer-arrow-callback": "off", - "prefer-const": "off", - "prefer-numeric-literals": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - "quote-props": "off", - "quotes": ["error", "double", { "avoidEscape": true }], - "require-jsdoc": "error", - "rest-spread-spacing": "error", - "semi": "off", - "semi-spacing": "error", - "sort-imports": "off", - "sort-keys": "off", - "space-before-blocks": "error", - "space-before-function-paren": "off", - "space-in-parens": [ - "error", - "never" - ], - "space-infix-ops": "error", - "space-unary-ops": "error", - "spaced-comment": [ - "error", - "always" - ], - "strict": "error", - "symbol-description": "error", - "template-curly-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "vars-on-top": "error", - "wrap-iife": "error", - "wrap-regex": "error", - "yield-star-spacing": "error", - "yoda": "error", - "import/no-named-as-default": "off", - "import/no-extraneous-dependencies": "off" - } -} + "extends": "@decidim" +} \ No newline at end of file diff --git a/.github/workflows/tests-legacy.yml b/.github/workflows/tests-legacy.yml index b866ba1f0..efa888124 100644 --- a/.github/workflows/tests-legacy.yml +++ b/.github/workflows/tests-legacy.yml @@ -60,6 +60,8 @@ jobs: run: bundle exec rspec spec/${{ matrix.rspec }} env: FEATURES: ${{ matrix.features }} + SIMPLECOV: 1 + CODECOV: 1 - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9ccaf6fe8..f4f356cc1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,6 +60,8 @@ jobs: run: bundle exec rspec spec/${{ matrix.rspec }} env: FEATURES: ${{ matrix.features }} + SIMPLECOV: 1 + CODECOV: 1 - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c74aefca..17a02f8c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +v0.9.1 +------ + +Compatibility: + - Decidim v0.27.x + - Decidim v0.26.x + +Features: + - Fixes for the Awesome Map + - Added Admin Accountability feature + v0.9.0 ------ diff --git a/Gemfile b/Gemfile index ba7d21ce8..3f6877fe3 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ group :development do gem "rubocop-faker" gem "spring", "~> 2.0" gem "spring-watcher-listen", "~> 2.0.0" - gem "web-console", "~> 3.5" + gem "web-console" end group :test do diff --git a/Gemfile.legacy.lock b/Gemfile.legacy.lock index 40332368c..ebff3affd 100644 --- a/Gemfile.legacy.lock +++ b/Gemfile.legacy.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decidim-decidim_awesome (0.9.0) + decidim-decidim_awesome (0.9.1) decidim-admin (>= 0.26.0, < 0.28) decidim-core (>= 0.26.0, < 0.28) sassc (~> 2.3) diff --git a/Gemfile.lock b/Gemfile.lock index 62f8543ce..961fdc917 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decidim-decidim_awesome (0.9.0) + decidim-decidim_awesome (0.9.1) decidim-admin (>= 0.26.0, < 0.28) decidim-core (>= 0.26.0, < 0.28) sassc (~> 2.3) @@ -332,13 +332,13 @@ GEM declarative-option (0.1.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - devise (4.8.1) + devise (4.9.0) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.10.2) + devise-i18n (1.10.3) devise (>= 4.8.0) devise_invitable (2.0.7) actionmailer (>= 5.0) @@ -510,6 +510,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) + nokogiri (1.13.10-x86_64-darwin) + racc (~> 1.4) nokogiri (1.13.10-x86_64-linux) racc (~> 1.4) oauth (1.1.0) @@ -555,7 +557,7 @@ GEM parallel (1.22.1) parallel_tests (3.13.0) parallel - parser (3.2.0.0) + parser (3.2.1.0) ast (~> 2.4.1) pg (1.1.4) pg_search (2.3.6) @@ -627,8 +629,8 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redcarpet (3.6.0) - redis (4.8.0) - regexp_parser (2.6.2) + redis (4.8.1) + regexp_parser (2.7.0) request_store (1.5.1) rack (>= 1.4) responders (3.1.0) @@ -675,8 +677,8 @@ GEM rubocop-ast (>= 1.17.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) rubocop-faker (1.1.0) faker (>= 2.12.0) rubocop (>= 0.82.0) @@ -739,8 +741,8 @@ GEM unicode-display_width (>= 1.1.1, < 3) thor (1.2.1) thread_safe (0.3.6) - tilt (2.0.11) - timeout (0.3.1) + tilt (2.1.0) + timeout (0.3.2) tomlrb (2.0.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -766,11 +768,11 @@ GEM rexml (~> 3.2) warden (1.2.9) rack (>= 2.0.9) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) bindex (>= 0.4.0) - railties (>= 5.0) + railties (>= 6.0.0) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -793,9 +795,10 @@ GEM wkhtmltopdf-binary (0.12.6.6) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.7) PLATFORMS + x86_64-darwin-22 x86_64-linux DEPENDENCIES @@ -813,7 +816,7 @@ DEPENDENCIES spring (~> 2.0) spring-watcher-listen (~> 2.0.0) uglifier (~> 4.1) - web-console (~> 3.5) + web-console RUBY VERSION ruby 3.0.5p211 diff --git a/README.md b/README.md index a4765d684..e90ee519e 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,14 @@ Rules available: ![Custom validations](examples/custom_validations.png) +#### 15. Admin accountability + +This feature allows you to list all the users that are, or have been at any point in time, admins, valuators, user managers or any other role in Decidim. Including global admin roles or private admins of a particular participatory space. + +Results can be filtered by role and by time range and also exported as CSV or other formats. + +![Admin accountability](examples/admin_accountability.png) + #### To be continued... We're not done! Please check the [issues](/decidim-ice/decidim-module-decidim_awesome/issues) (and participate) to see what's on our mind diff --git a/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable.rb b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable.rb new file mode 100644 index 000000000..c6e790c67 --- /dev/null +++ b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module Decidim + module DecidimAwesome + module AdminAccountability + module Admin + module Filterable + extend ActiveSupport::Concern + + included do + include Decidim::Admin::Filterable + + helper Decidim::DecidimAwesome::AdminAccountability::Admin::FilterableHelper + + private + + def base_query + collection + end + + def filters + [:role_type_eq, :participatory_space_type_eq] + end + + def filters_with_values + return { admin_role_type: [] } if global? + + { role_type_eq: role_types, participatory_space_type_eq: participatory_space_types } + end + + def dynamically_translated_filters + [:role_type_eq, :participatory_space_type_eq] + end + + def extra_allowed_params + [:per_page, :admins, :admin_role_type] + end + + def translated_role_type_eq(role) + I18n.t(role, scope: "decidim.decidim_awesome.admin.admin_accountability.roles") + end + + def translated_participatory_space_type_eq(item_type) + item_type.gsub("UserRole", "").safe_constantize&.model_name&.human&.pluralize || item_type + end + + def search_field_predicate + :user_name_or_user_email_cont + end + + def participatory_space_types + @participatory_space_types ||= collection.pluck(:item_type).uniq.sort + end + + def role_types + @role_types ||= PaperTrailVersion.safe_user_roles.map do |role_class| + role_class.safe_constantize.select(:role).distinct.pluck(:role) + end.union.flatten.sort + end + end + end + end + end + end +end diff --git a/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb new file mode 100644 index 000000000..3da344bd9 --- /dev/null +++ b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module AdminAccountability + module Admin + module FilterableHelper + def extra_dropdown_submenu_options_items(_filter, _i18n_scope) + Decidim.user_roles.sort.map do |role_type| + link_to(I18n.t(role_type, scope: "decidim.decidim_awesome.admin.admin_accountability.admin_roles"), + url_for(export_params.merge({ admin_role_type: role_type }))) + end + end + + def applied_filters_tags(i18n_ctx) + if global? && params[:admin_role_type].present? + content_tag(:span, class: "label secondary") do + concat "#{i18n_filter_label(:admin_role_type, filterable_i18n_scope_from_ctx(i18n_ctx))}: " + concat t("decidim.decidim_awesome.admin.admin_accountability.admin_roles.#{params[:admin_role_type]}", default: params[:admin_role_type]) + concat icon_link_to( + "circle-x", + url_for(export_params.except(:admin_role_type)), + t("decidim.admin.actions.cancel"), + class: "action-icon--remove" + ) + end + else + ransack_params.slice(*filters).map do |filter, value| + applied_filter_tag(filter, value, filterable_i18n_scope_from_ctx(i18n_ctx)) + end.join.html_safe + end + end + end + end + end + end +end diff --git a/app/controllers/decidim/decidim_awesome/admin/admin_accountability_controller.rb b/app/controllers/decidim/decidim_awesome/admin/admin_accountability_controller.rb new file mode 100644 index 000000000..54e3708ea --- /dev/null +++ b/app/controllers/decidim/decidim_awesome/admin/admin_accountability_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Admin + class AdminAccountabilityController < DecidimAwesome::Admin::ApplicationController + include NeedsAwesomeConfig + include Decidim::DecidimAwesome::AdminAccountability::Admin::Filterable + + helper_method :admin_actions, :collection, :export_params, :global? + + layout "decidim/admin/users" + + before_action do + enforce_permission_to :edit_config, :admin_accountability, global: global? + end + + def index; end + + def export + filters = export_params[:q] + + Decidim::DecidimAwesome::ExportAdminActionsJob.perform_later(current_user, + params[:format].to_s, + admin_actions.ransack(filters).result.ids) + + redirect_back fallback_location: decidim_admin_decidim_awesome.admin_accountability_path, + notice: t("decidim.decidim_awesome.admin.admin_accountability.exports.notice") + end + + private + + def admin_actions + @admin_actions ||= filtered_collection + end + + def collection + @collection ||= paginate(global? ? PaperTrailVersion.admin_role_actions(params[:admin_role_type]) : PaperTrailVersion.space_role_actions) + end + + def export_params + params.permit(:format, :admins, :admin_role_type, q: [:role_type_eq, :user_name_or_user_email_cont, :created_at_gteq, :created_at_lteq]) + end + + def global? + params[:admins] == "true" + end + end + end + end +end diff --git a/app/jobs/decidim/decidim_awesome/export_admin_actions_job.rb b/app/jobs/decidim/decidim_awesome/export_admin_actions_job.rb new file mode 100644 index 000000000..a4672a975 --- /dev/null +++ b/app/jobs/decidim/decidim_awesome/export_admin_actions_job.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + class ExportAdminActionsJob < ApplicationJob + queue_as :default + + def perform(current_user, format, collection_ids) + collection = serialized_collection(collection_ids) + + export_data = Exporters.find_exporter(format).new(collection).export + + ExportMailer.export(current_user, "admin_actions", export_data).deliver_now + end + + private + + def serialized_collection(collection_ids) + @serialized_collection ||= begin + collection = PaperTrailVersion.where(id: collection_ids) + collection.map do |item| + PaperTrailVersionSerializer.new(item).serialize + end + end + end + end + end +end diff --git a/app/models/decidim/decidim_awesome/paper_trail_version.rb b/app/models/decidim/decidim_awesome/paper_trail_version.rb new file mode 100644 index 000000000..efb6d60cf --- /dev/null +++ b/app/models/decidim/decidim_awesome/paper_trail_version.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + class PaperTrailVersion < PaperTrail::Version + default_scope { order("created_at DESC") } + + def self.safe_user_roles + DecidimAwesome.participatory_space_roles.filter(&:safe_constantize) + end + + scope :space_role_actions, -> { where(item_type: PaperTrailVersion.safe_user_roles, event: "create") } + + def self.admin_role_actions(filter = nil) + base = where(item_type: "Decidim::UserBaseEntity", event: %w(create update)) + case filter + when nil + base.where("object_changes LIKE '%\nroles:\n%' OR object_changes LIKE '%\nadmin:\n- false\n%'") + when "admin" + base.where("object_changes LIKE '%\nadmin:\n- false\n%'") + else + base.where(Arel.sql("object_changes LIKE '%\nroles:\n%\n- - #{filter}\n%'")) + end + end + + def present(html: true) + @present ||= if item_type == "Decidim::UserBaseEntity" + UserEntityPresenter.new(self, html: html) + elsif item_type.in?(PaperTrailVersion.safe_user_roles) + ParticipatorySpaceRolePresenter.new(self, html: html) + else + self + end + end + + ransacker :role_type do + @role_type ||= begin + queries = PaperTrailVersion.safe_user_roles.map do |role_class| + table = role_class.safe_constantize.table_name + %{ + SELECT ("#{table}"."role")::text FROM "#{table}" + WHERE "#{table}"."id" = "versions"."item_id" + AND item_type = '#{role_class}' + } + end + Arel.sql("(#{queries.join(" UNION ")})") + end + end + + ransacker :participatory_space_type do + Arel.sql(%{("item_type")::text}) + end + + ransacker :user_email do + @user_email ||= begin + queries = PaperTrailVersion.safe_user_roles.map do |role_class| + table = role_class.safe_constantize.table_name + %( + SELECT decidim_users.email FROM decidim_users + JOIN #{table} ON decidim_users.id = #{table}.decidim_user_id + WHERE #{table}.id = versions.item_id + AND item_type = '#{role_class}' + ) + end + queries << %( + SELECT decidim_users.email FROM decidim_users + WHERE decidim_users.id = versions.item_id + AND item_type = 'Decidim::UserBaseEntity' + ) + Arel.sql("(#{queries.join(" UNION ")})") + end + end + + ransacker :user_name do + @user_name ||= begin + queries = PaperTrailVersion.safe_user_roles.map do |role_class| + table = role_class.safe_constantize.table_name + %( + SELECT decidim_users.name FROM decidim_users + JOIN #{table} ON decidim_users.id = #{table}.decidim_user_id + WHERE #{table}.id = versions.item_id + AND item_type = '#{role_class}' + ) + end + queries << %( + SELECT decidim_users.name FROM decidim_users + WHERE decidim_users.id = versions.item_id + AND item_type = 'Decidim::UserBaseEntity' + ) + Arel.sql("(#{queries.join(" UNION ")})") + end + end + + ransacker :created_at, type: :date do + Arel.sql("date(created_at)") + end + end + end +end diff --git a/app/packs/entrypoints/decidim_admin_decidim_awesome.js b/app/packs/entrypoints/decidim_admin_decidim_awesome.js index a14ad8f14..c88a74714 100644 --- a/app/packs/entrypoints/decidim_admin_decidim_awesome.js +++ b/app/packs/entrypoints/decidim_admin_decidim_awesome.js @@ -1,5 +1,6 @@ -import "src/decidim/decidim_awesome/awesome_admin" -import "jquery-ui/ui/widgets/sortable" // This is needed by custom fields builder but if loader there duplicates the jQuery inclusion +import "src/decidim/decidim_awesome/awesome_admin"; +// This is needed by custom fields builder but if loader there duplicates the jQuery inclusion +import "jquery-ui/ui/widgets/sortable"; // CSS import "entrypoints/decidim_admin_decidim_awesome.scss"; diff --git a/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js b/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js index 9824efa76..faa9fc668 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js +++ b/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js @@ -1,13 +1,13 @@ $(() => { let CustomFieldsBuilders = window.CustomFieldsBuilders || []; - $("body").on("click", "a.awesome-auto-edit", (e) => { - e.preventDefault(); - const $link = $(e.currentTarget); + $("body").on("click", "a.awesome-auto-edit", (ev) => { + ev.preventDefault(); + const $link = $(ev.currentTarget); const scope = $link.data("scope"); const $target = $(`span.awesome-auto-edit[data-scope="${scope}"]`); const $constraints = $(`.constraints-editor[data-key="${scope}"]`); - if ($target.length == 0) { + if ($target.length === 0) { return; } @@ -41,7 +41,7 @@ $(() => { $container.attr("data-key", result.key); $delete.attr("href", $delete.attr("href").replace(`key=${key}`, `key=${result.key}`)) CustomFieldsBuilders.forEach((builder) => { - if (builder.key == key) { + if (builder.key === key) { builder.key = result.key; } }); @@ -52,12 +52,12 @@ $(() => { $link.hide(); $input.select(); $input.on("keypress", (evt) => { - if (evt.code == "Enter" || evt.code == "13" || evt.code == "10") { + if (evt.code === "Enter" || evt.code === "13" || evt.code === "10") { evt.preventDefault(); $.ajax( { type: "POST", - url: DecidimAwesome.rename_scope_label_path, + url: window.DecidimAwesome.rename_scope_label_path, dataType: "json", headers: { "X-CSRF-Token": $("meta[name=csrf-token]").attr("content") diff --git a/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js b/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js index 869c3df72..97a11b66a 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js +++ b/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js @@ -14,13 +14,13 @@ $(() => { let type = response.type; let status = response.status; - if (response.type == "opaqueredirect") { + if (response.type === "opaqueredirect") { type = "redirect"; status = "302"; } if (item.active) { - if (type == "redirect") { + if (type === "redirect") { $td.addClass("success"); } else { $td.addClass("alert"); diff --git a/app/packs/src/decidim/decidim_awesome/admin/constraints.js b/app/packs/src/decidim/decidim_awesome/admin/constraints.js index 9d72ad1f3..d8c88ce05 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/constraints.js +++ b/app/packs/src/decidim/decidim_awesome/admin/constraints.js @@ -4,9 +4,9 @@ $(() => { return; } - $(".decidim_awesome-form").on("click", ".constraints-editor .add-condition,.constraints-editor .edit-condition", (e) => { - e.preventDefault(); - const $this = $(e.target) + $(".decidim_awesome-form").on("click", ".constraints-editor .add-condition,.constraints-editor .edit-condition", (evt) => { + evt.preventDefault(); + const $this = $(evt.target) const url = $this.attr("href"); const $callout = $this.closest(".constraints-editor").find(".callout"); $callout.hide(); @@ -21,8 +21,8 @@ $(() => { }); // Custom event listener to reload the modal if needed - document.body.addEventListener("constraint:change", (e) => { - const vars = e.detail.map((setting) => `${setting.key}=${setting.value}`); + document.body.addEventListener("constraint:change", (evt) => { + const vars = evt.detail.map((setting) => `${setting.key}=${setting.value}`); const url = `${$modal.data("url")}&${vars.join("&")}`; // console.log("constraint:change vars:", vars, "url:", url) $modal.addClass("loading"); diff --git a/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js index 5c58ab37c..2356aeb9b 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js +++ b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js @@ -36,7 +36,8 @@ $(() => { "paragraph" ], disabledSubtypes: { - text: ["color"], // default color as it generate hashtags in decidim (TODO: fix hashtag generator with this) + // default color as it generate hashtags in decidim (TODO: fix hashtag generator with this) + text: ["color"], // disable default wysiwyg editors as they present problems textarea: ["tinymce", "quill"] } @@ -45,21 +46,21 @@ $(() => { }); }); - $(document).on("formBuilder.create", (_event, i, list) => { - if (!list[i]) { + $(document).on("formBuilder.create", (_event, idx, list) => { + if (!list[idx]) { return; } - $(list[i].el).formBuilder(list[i].config).promise.then(function(res) { - list[i].instance = res; + $(list[idx].el).formBuilder(list[idx].config).promise.then(function(res) { + list[idx].instance = res; // Attach to DOM - list[i].el.FormBuilder = res; + list[idx].el.FormBuilder = res; // remove spinner - $(list[i].el).find(".loading-spinner").remove(); + $(list[idx].el).find(".loading-spinner").remove(); // for external use - $(document).trigger("formBuilder.created", [list[i]]); - if (i < list.length) { - $(document).trigger("formBuilder.create", [i + 1, list]); + $(document).trigger("formBuilder.created", [list[idx]]); + if (idx < list.length) { + $(document).trigger("formBuilder.create", [idx + 1, list]); } }); }); diff --git a/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js b/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js index 7cef6a7ff..f8810f245 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js +++ b/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js @@ -23,6 +23,7 @@ $(() => { } event.returnValue = true; + return true; }); } }); diff --git a/app/packs/src/decidim/decidim_awesome/admin/user_picker.js b/app/packs/src/decidim/decidim_awesome/admin/user_picker.js index b1eb356f1..9f3a1dfcd 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/user_picker.js +++ b/app/packs/src/decidim/decidim_awesome/admin/user_picker.js @@ -1,3 +1,4 @@ +/* eslint-disable no-invalid-this */ import "select2" import "stylesheets/decidim/decidim_awesome/admin/user_picker.scss" diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js b/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js index 9b75068cd..c73fbb05d 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js @@ -67,13 +67,13 @@ export default class Fetcher { } findTranslation(translations) { - let text, - lang = document.querySelector("html").getAttribute("lang"); + let lang = document.querySelector("html").getAttribute("lang"), + text = ""; - translations.forEach((t) => { - if (t.text) { - if (!text || t.locale == lang) { - text = t.text + translations.forEach((txt) => { + if (txt.text) { + if (!text || txt.locale === lang) { + text = txt.text } } }); @@ -85,12 +85,10 @@ export default class Fetcher { if (text) { const gids = text.match(/gid:\/\/[^\s<&]+/g) if (gids) { - tags = gids.filter((gid) => gid.indexOf("/Decidim::Hashtag/") != -1).map((gid) => { + tags = gids.filter((gid) => gid.indexOf("/Decidim::Hashtag/") !== -1).map((gid) => { const parts = gid.split("/"); - const fromSelector = parts[5].charAt(0) == "_"; - const tag = fromSelector - ? parts[5].substr(1) - : parts[5]; + const fromSelector = parts[5].charAt(0) === "_"; + const tag = fromSelector ? parts[5].substr(1) : parts[5]; // eslint-disable-line no-ternary, multiline-ternary const name = `#${tag}`; const html = `${name}`; const hashtag = { @@ -110,7 +108,8 @@ export default class Fetcher { return tags; } - replaceHashtags(text, hashtags) { + replaceHashtags(txt, hashtags) { + let text = txt; hashtags.forEach((tag) => { text = text.replace(tag.gid, tag.name) }); @@ -121,7 +120,8 @@ export default class Fetcher { return text.replace(/gid:\/\/[^\s<&]+/g, ""); } - appendHtmlHashtags(text, tags) { + appendHtmlHashtags(txt, tags) { + let text = txt; tags.forEach((tag) => { text += ` ${tag.html}`; }); diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js b/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js index 62bc033c8..689874b36 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js @@ -1,8 +1,11 @@ import * as L from "leaflet"; -import "src/decidim/map/icon.js" // comes with Decidim +// comes with Decidim +import "src/decidim/map/icon.js" import "src/decidim/vendor/leaflet-tilelayer-here" -import "leaflet.markercluster"; // Comes with Decidim -import "leaflet.featuregroup.subgroup" // included in this package.json +// Comes with Decidim +import "leaflet.markercluster"; +// included in this package.json +import "leaflet.featuregroup.subgroup" import "src/vendor/jquery.truncate" import "jsrender" @@ -60,7 +63,7 @@ export default class AwesomeMap { this.loading.pop(); this.autoResize(); - if (this.loading.length == 0) { + if (this.loading.length === 0) { this.controls.$loading.hide(); // call trigger as all loads are finished this.onFinished(); @@ -90,13 +93,11 @@ export default class AwesomeMap { }; if (category) { - let id = category.id - ? parseInt(category.id, 10) - : parseInt(category, 10); - let cat = this.categories.find((c) => c.id == id); + let id = category.id ? parseInt(category.id, 10) : parseInt(category, 10); // eslint-disable-line no-ternary, multiline-ternary + let cat = this.categories.find((ct) => ct.id === id); if (cat) { cat.children = () => { - return this.categories.filter((c) => c.parent === cat.id); + return this.categories.filter((ct) => ct.parent === cat.id); } return cat; } @@ -105,12 +106,12 @@ export default class AwesomeMap { } _getController(component) { - let controller; + let controller = null; - if (component.type == "proposals") { + if (component.type === "proposals") { controller = new ProposalsController(this, component); } - if (component.type == "meetings" && this.config.menu.meetings) { + if (component.type === "meetings" && this.config.menu.meetings) { controller = new MeetingsController(this, component); } @@ -125,5 +126,6 @@ export default class AwesomeMap { this.controllers[component.type] = controller; return this.controllers[component.type] } + return null; } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js index 44e9be785..55d120cdc 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js @@ -14,9 +14,10 @@ export default class Controller { } getLabel() { - let text = this.awesomeMap.config.menu.mergeComponents || !this.component.name - ? window.DecidimAwesome.texts[this.component.type] - : this.component.name; + let text = this.awesomeMap.config.menu.mergeComponents || this.component.name; + if (!text) { + text = window.DecidimAwesome.texts[this.component.type]; + } return `${text}` } @@ -32,8 +33,8 @@ export default class Controller { const collectionEdges = collection.edges.filter((item) => item.node.coordinates && item.node.coordinates.latitude && item.node.coordinates.longitude); try { this.awesomeMap.cluster.addLayers(collectionEdges.map((item) => item.node.marker)); - } catch (e) { - console.error("Failed marker collection assignation", collectionEdges, "error", e); + } catch (evt) { + console.error("Failed marker collection assignation", collectionEdges, "error", evt); } // subgroups don't have th addLayers utility collectionEdges.forEach((item) => { @@ -97,8 +98,8 @@ export default class Controller { try { this.awesomeMap.layers[cat.id].group.addLayer(marker); this.awesomeMap.controls.showCategory(cat); - } catch (e) { - console.error("Failed category marker assignation", marker, e.message); + } catch (evt) { + console.error("Failed category marker assignation", marker, evt.message); } } } @@ -108,8 +109,8 @@ export default class Controller { if (this.awesomeMap.config.menu.hashtags) { try { this.awesomeMap.controls.addHashtagsControls(hashtags, marker); - } catch (e) { - console.error("Failed hashtags marker assignation", marker, e.message); + } catch (evt) { + console.error("Failed hashtags marker assignation", marker, evt.message); } } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js index c1d50388a..d1ae68b44 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js @@ -62,15 +62,15 @@ export default class ProposalsController extends Controller { // Process all amendments iterableAmendments.forEach((amendment) => { - const marker = this.allNodes.find((node) => node.id == amendment[0]); + const marker = this.allNodes.find((node) => node.id === amendment[0]); const parent = amendment[1]; // console.log("marker", marker, "parent proposal", parent) // add marker to amendments layers and remove it from proposals if (marker) { try { marker.marker.removeFrom(this.controls.group) - } catch (e) { - console.error("error removeFrom marker", marker, "layer", this.controls.group, e); + } catch (evt) { + console.error("error removeFrom marker", marker, "layer", this.controls.group, evt); } if (this.awesomeMap.config.menu.amendments) { marker.marker.addTo(this.awesomeMap.layers.amendments.group); diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js index 8d3d0a3c6..d4d8a358c 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js @@ -1,3 +1,5 @@ +/* eslint-disable no-ternary, multiline-ternary */ + import * as L from "leaflet"; export default class ControlsUI { @@ -33,28 +35,28 @@ export default class ControlsUI { } // sub-layer hashtag title toggle - $("#awesome-map").on("click", ".awesome_map-title-control", (e) => { - e.preventDefault(); - e.stopPropagation(); + $("#awesome-map").on("click", ".awesome_map-title-control", (evt) => { + evt.preventDefault(); + evt.stopPropagation(); $("#awesome_map-categories-control").toggleClass("active"); $("#awesome_map-hashtags-control").toggleClass("active"); }); // hashtag events - $("#awesome-map").on("change", ".awesome_map-hashtags-selector", (e) => { - e.preventDefault(); - e.stopPropagation(); - const tag = $(e.target).closest("label").data("layer"); - // console.log("changed, layer", tag, "checked", e.target.checked, e); + $("#awesome-map").on("change", ".awesome_map-hashtags-selector", (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + const tag = $(evt.target).closest("label").data("layer"); + // console.log("changed, layer", tag, "checked", evt.target.checked, e); if (tag) { this.updateHashtagLayers(); } }); // select/deselect all tags - $("#awesome-map").on("click", ".awesome_map-toggle_all_tags", (e) => { - e.preventDefault(); - e.stopPropagation(); + $("#awesome-map").on("click", ".awesome_map-toggle_all_tags", (evt) => { + evt.preventDefault(); + evt.stopPropagation(); $("#awesome-map .awesome_map-hashtags-selector").prop("checked", $("#awesome-map .awesome_map-hashtags-selector:checked").length < $("#awesome-map .awesome_map-hashtags-selector").length); this.updateHashtagLayers(); }); @@ -74,22 +76,20 @@ export default class ControlsUI { group: new L.FeatureGroup.SubGroup(this.awesomeMap.cluster) }; this.awesomeMap.layers[category.id].group.addTo(this.awesomeMap.map); - $("#awesome_map-categories-control .categories-container").append(``); + $("#awesome_map-categories-control .categories-container").append(``); }) // category events - $("#awesome-map").on("change", ".awesome_map-categories-selector", (e) => { - e.preventDefault(); - e.stopPropagation(); + $("#awesome-map").on("change", ".awesome_map-categories-selector", (evt) => { + evt.preventDefault(); + evt.stopPropagation(); - const id = $(e.target).closest("label").data("layer"); + const id = $(evt.target).closest("label").data("layer"); const cat = this.awesomeMap.getCategory(id); - // console.log("changed, layer", id, "cat", cat, "checked", e.target.checked, e); + // console.log("changed, layer", id, "cat", cat, "checked", evt.target.checked, e); if (cat) { const layer = this.awesomeMap.layers[cat.id]; - if (e.target.checked) { + if (evt.target.checked) { // show group of markers this.awesomeMap.map.addLayer(layer.group); } else { @@ -131,7 +131,7 @@ export default class ControlsUI { const $label = $(`label.awesome_map-hashtag-${hashtag.tag}`); // update number of items - $label.attr("title", `${parseInt($label.attr("title") || 0) + 1} ${window.DecidimAwesome.texts.items}`); + $label.attr("title", `${parseInt($label.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); }); } } @@ -143,10 +143,10 @@ export default class ControlsUI { const $parent = $(`label.awesome_map-category-${cat.parent}`); $label.show(); // update number of items - $label.attr("title", `${parseInt($label.attr("title") || 0) + 1} ${window.DecidimAwesome.texts.items}`); + $label.attr("title", `${parseInt($label.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); // show parent if apply $parent.show(); - $parent.attr("title", `${parseInt($parent.attr("title") || 0) + 1} ${window.DecidimAwesome.texts.items}`); + $parent.attr("title", `${parseInt($parent.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); } removeHiddenComponents() { @@ -200,8 +200,8 @@ export default class ControlsUI { if (cat.parent) { let $input = $(`.awesome_map-category-${cat.parent}`).contents("input"); let $subcats = $(`[class^="awesome_map-category-"][data-parent="${cat.parent}"]:visible`); - let num_checked = $subcats.contents("input:checked").length; - $input.prop("indeterminate", num_checked != $subcats.length && num_checked != 0); + let numChecked = $subcats.contents("input:checked").length; + $input.prop("indeterminate", numChecked !== $subcats.length && numChecked !== 0); } } @@ -213,7 +213,6 @@ export default class ControlsUI { $div.contents("label").each((_idx, el) => { if ($(el).text().localeCompare($last.text()) > 0) { $(el).before($last); - return false; } }); } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js b/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js index 86979ed76..a3fd7a1cc 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js @@ -12,6 +12,7 @@ $(() => { } } } + return null; }; const config = { diff --git a/app/packs/src/decidim/decidim_awesome/editors/editor.js b/app/packs/src/decidim/decidim_awesome/editors/editor.js index 5709888c8..866d4f1dd 100644 --- a/app/packs/src/decidim/decidim_awesome/editors/editor.js +++ b/app/packs/src/decidim/decidim_awesome/editors/editor.js @@ -1,4 +1,4 @@ -/* eslint-disable require-jsdoc */ +/* eslint-disable require-jsdoc, func-style */ /* * Since version 0.25 we follow a different strategy and opt to destroy and override completely the original editor @@ -26,8 +26,8 @@ export function destroyQuillEditor(container) { const content = $(container).find(".ql-editor").html(); $(container).html(content); $(container).siblings(".ql-toolbar").remove(); - $(container).find("*[class*='ql-']").removeClass((index, class_name) => (class_name.match(/(^|\s)ql-\S+/g) || []).join(" ")); - $(container).removeClass((index, class_name) => (class_name.match(/(^|\s)ql-\S+/g) || []).join(" ")); + $(container).find("*[class*='ql-']").removeClass((index, className) => (className.match(/(^|\s)ql-\S+/g) || []).join(" ")); + $(container).removeClass((index, className) => (className.match(/(^|\s)ql-\S+/g) || []).join(" ")); if ($(container).next().is("p.help-text")) { $(container).next().remove(); } @@ -107,7 +107,7 @@ export function createQuillEditor(container) { let msg = serverError && serverError.body; try { msg = JSON.parse(msg).message; - } catch (e) { console.error("Parsing error", e); } + } catch (evt) { console.error("Parsing error", evt); } console.error(`Image upload error: ${msg}`); let $p = $(`

${msg}

`); $(container).after($p) @@ -174,7 +174,7 @@ export function createQuillEditor(container) { } export function createMarkdownEditor(container) { - const t = DecidimAwesome.texts.drag_and_drop_image; + const text = DecidimAwesome.texts.drag_and_drop_image; const token = $('meta[name="csrf-token"]').attr("content"); const $input = $(container).siblings('input[type="hidden"]'); const $faker = $('