Merge pull request #10876 from demarches-simplifiees/feat/10333

ETQ admin, dans la page "toutes les démarches", je veux que les tags soient harmonisés
This commit is contained in:
Kara Diaby 2024-10-14 08:45:59 +00:00 committed by GitHub
commit 8e1b636901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 328 additions and 48 deletions

View file

@ -42,7 +42,7 @@ trix-editor.fr-input {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-bottom: 0.3rem;
margin-bottom: 0.5rem;
}
}

View file

@ -476,7 +476,16 @@ module Administrateurs
procedures_result = procedures_result.where(procedures_zones: { zone_id: filter.zone_ids }) if filter.zone_ids.present?
procedures_result = procedures_result.where(hidden_at_as_template: nil)
procedures_result = procedures_result.where(aasm_state: filter.statuses) if filter.statuses.present?
procedures_result = procedures_result.where("tags @> ARRAY[?]::text[]", filter.tags) if filter.tags.present?
if filter.tags.present?
tag_ids = ProcedureTag.where(name: filter.tags).pluck(:id).flatten
if tag_ids.any?
procedures_result = procedures_result
.joins(:procedure_tags)
.where(procedure_tags: { id: tag_ids })
.distinct
end
end
procedures_result = procedures_result.where(template: true) if filter.template?
procedures_result = procedures_result.where(published_at: filter.from_publication_date..) if filter.from_publication_date.present?
procedures_result = procedures_result.where(service: service) if filter.service_siret.present?
@ -532,7 +541,7 @@ module Administrateurs
:lien_dpo,
:opendata,
:procedure_expires_when_termine_enabled,
{ zone_ids: [], tags: [] }
{ zone_ids: [], procedure_tag_names: [] }
]
editable_params << :piece_justificative_multiple if @procedure && !@procedure.piece_justificative_multiple?
@ -545,6 +554,12 @@ module Administrateurs
if permited_params[:auto_archive_on].present?
permited_params[:auto_archive_on] = Date.parse(permited_params[:auto_archive_on]) + 1.day
end
if permited_params[:procedure_tag_names].present?
tag_ids = ProcedureTag.where(name: permited_params[:procedure_tag_names]).pluck(:id)
permited_params[:procedure_tag_ids] = tag_ids
permited_params.delete(:procedure_tag_names)
end
permited_params
end

View file

@ -0,0 +1,6 @@
# frozen_string_literal: true
module Manager
class ProcedureTagsController < Manager::ApplicationController
end
end

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
require "administrate/base_dashboard"
class ProcedureTagDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
name: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:id,
:name
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:id,
:name,
:created_at,
:updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [
:name
].freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze
# Overwrite this method to customize how procedure tags are displayed
# across all pages of the admin dashboard.
#
def display_resource(procedure_tag)
"ProcedureTag ##{procedure_tag.id}"
end
end

View file

@ -56,6 +56,7 @@ class Procedure < ApplicationRecord
belongs_to :service, optional: true
belongs_to :zone, optional: true
has_and_belongs_to_many :zones
has_and_belongs_to_many :procedure_tags
has_many :bulk_messages, dependent: :destroy

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class ProcedureTag < ApplicationRecord
has_and_belongs_to_many :procedures
validates :name, presence: true, uniqueness: { case_sensitive: false }
end

View file

@ -0,0 +1,110 @@
# frozen_string_literal: true
# this task is used to create the procedure_tags and backfill the procedures that have the tag in their tags array
module Maintenance
class CreateProcedureTagsTask < MaintenanceTasks::Task
include RunnableOnDeployConcern
include StatementsHelpersConcern
run_on_first_deploy
def collection
[
"Aap",
"Accompagnement",
"Action sociale",
"Adeli",
"Affectation",
"Agrément",
"Agriculture",
"agroécologie",
"Aide aux entreprises",
"Aide financière",
"Appel à manifestation d'intérêt",
"AMI",
"Animaux",
"Appel à projets",
"Association",
"Auto-école",
"Autorisation",
"Autorisation d'exercer",
"Bilan",
"Biodiversité",
"Candidature",
"Cerfa",
"Chasse",
"Cinéma",
"Cmg",
"Collectivé territoriale",
"Collège",
"Convention",
"Covid",
"Culture",
"Dérogation",
"Diplôme",
"Drone",
"DSDEN",
"Eau",
"Ecoles",
"Education",
"Elections",
"Energie",
"Enseignant",
"ENT",
"Environnement",
"Étrangers",
"Formation",
"FPRNM",
"Funéraire",
"Handicap",
"Hygiène",
"Industrie",
"innovation",
"Inscription",
"Logement",
"Lycée",
"Manifestation",
"Médicament",
"Micro-crèche",
"MODELE DS",
"Numérique",
"Permis",
"Pompiers",
"Préfecture",
"Professionels de santé",
"Recrutement",
"Rh",
"Santé",
"Scolaire",
"SDIS",
"Sécurité",
"Sécurité routière",
"Sécurité sociale",
"Séjour",
"Service civique",
"Subvention",
"Supérieur",
"Taxi",
"Télétravail",
"Tirs",
"Transition écologique",
"Transport",
"Travail",
"Université",
"Urbanisme"
]
end
def process(tag)
procedure_tag = ProcedureTag.find_or_create_by(name: tag)
Procedure.where("? ILIKE ANY(tags)", tag).find_each(batch_size: 500) do |procedure|
procedure.procedure_tags << procedure_tag unless procedure.procedure_tags.include?(procedure_tag)
end
end
def count
collection.size
end
end
end

View file

@ -121,15 +121,17 @@
.fr-fieldset__element
= f.label :tags, 'Associez des thématiques à la démarche', class: 'fr-label'
%p.fr-hint-text Par des mots ou des expressions que vous attribuez aux démarches pour décrire leur contenu et pour les retrouver. Les tags sont partagés avec la communauté, ce qui vous permet de voir les tags attribués aux démarches créées par les autres administrateurs.
%p.fr-hint-text
Par des mots ou des expressions que vous attribuez aux démarches pour décrire leur contenu et pour les retrouver.
Les thèmes sont partagées avec la communauté, ce qui vous permet de voir les thèmes attribués aux démarches créées par les autres administrateurs.
%react-fragment
= render ReactComponent.new "ComboBox/MultiComboBox",
id: "procedure_tags_combo",
items: Procedure.tags,
selected_keys: @procedure.tags,
name: 'procedure[tags][]',
items: ProcedureTag.order(:name).pluck(:name),
selected_keys: @procedure.procedure_tags.pluck(:name),
name: 'procedure[procedure_tag_names][]',
value_separator: ',|;',
allows_custom_value: true,
allows_custom_value: false,
'aria-label': 'Tags',
'aria-describedby': 'procedure-tags'

View file

@ -25,6 +25,21 @@
%span.fr-icon-arrow-go-back-line Réinitialiser
%ul
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w
%button{ 'data-action': 'expand#toggle' }
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
Thématique
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
%div
= f.search_field :tags, placeholder: 'Choisissez un thème', list: 'tags_list', class: 'fr-input', data: { no_autosubmit: 'input', turbo_force: :server }, multiple: true
%datalist#tags_list
- ProcedureTag.order(:name).each do |tag|
%option{ value: tag.name, data: { id: tag.id } }
- if @filter.tags.present?
- @filter.tags.each do |tag|
= f.hidden_field :tags, value: tag, multiple: true, id: "tag-#{tag.tr(' ', '_')}"
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w
%button{ 'data-action': 'expand#toggle' }
@ -105,22 +120,6 @@
= b.check_box(checked: @filter.status_filtered?(b.value))
= b.label(class: 'fr-label') { t b.text, scope: 'activerecord.attributes.procedure.aasm_state' }
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
.fr-mb-1w
%button{ 'data-action': 'expand#toggle' }
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
Thématique
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
%div
= f.search_field :tags, placeholder: 'Choisissez un thème', list: 'tags_list', class: 'fr-input', data: { no_autosubmit: 'input', turbo_force: :server }, multiple: true
%datalist#tags_list
- Procedure.tags.each do |tag|
%option{ value: tag }
- if @filter.tags.present?
- @filter.tags.each do |tag|
= f.hidden_field :tags, value: tag, multiple: true, id: "tag-#{tag.tr(' ', '_')}"
.fr-col-9
= yield(:results)
= render template: 'layouts/application'

View file

@ -33,6 +33,8 @@ Rails.application.routes.draw do
resources :administrateur_confirmations, only: [:new, :create]
end
resources :procedure_tags, only: [:index, :show, :new, :create, :edit, :update, :destroy]
resources :archives, only: [:index, :show]
resources :dossiers, only: [:index, :show] do

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
class CreateProcedureTags < ActiveRecord::Migration[7.0]
def change
create_table :procedure_tags do |t|
t.string :name, null: false
t.timestamps
end
add_index :procedure_tags, :name, unique: true
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
class CreateJoinTableProceduresProcedureTags < ActiveRecord::Migration[7.0]
def change
create_join_table :procedures, :procedure_tags do |t|
t.index [:procedure_id, :procedure_tag_id], name: 'index_procedures_tags_on_procedure_id_and_tag_id'
t.index [:procedure_tag_id, :procedure_id], name: 'index_procedures_tags_on_tag_id_and_procedure_id'
end
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_09_23_125619) do
ActiveRecord::Schema[7.0].define(version: 2024_09_29_141825) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_buffercache"
enable_extension "pg_stat_statements"
@ -913,6 +913,21 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_125619) do
t.index ["procedure_id"], name: "index_procedure_revisions_on_procedure_id"
end
create_table "procedure_tags", force: :cascade do |t|
t.datetime "created_at", null: false
t.text "description"
t.string "name", null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_procedure_tags_on_name", unique: true
end
create_table "procedure_tags_procedures", id: false, force: :cascade do |t|
t.bigint "procedure_id", null: false
t.bigint "procedure_tag_id", null: false
t.index ["procedure_id", "procedure_tag_id"], name: "index_procedures_tags_on_procedure_id_and_tag_id"
t.index ["procedure_tag_id", "procedure_id"], name: "index_procedures_tags_on_tag_id_and_procedure_id"
end
create_table "procedures", id: :serial, force: :cascade do |t|
t.string "aasm_state", default: "brouillon"
t.boolean "accuse_lecture", default: false, null: false

View file

@ -15,7 +15,8 @@ describe Administrateurs::ProceduresController, type: :controller do
let(:lien_site_web) { 'http://mon-site.gouv.fr' }
let(:zone) { create(:zone) }
let(:zone_ids) { [zone.id] }
let(:tags) { ["planete", "environnement"] }
let!(:tag1) { ProcedureTag.create(name: 'Aao') }
let!(:tag2) { ProcedureTag.create(name: 'Accompagnement') }
describe '#apercu' do
subject { get :apercu, params: { id: procedure.id } }
@ -64,7 +65,7 @@ describe Administrateurs::ProceduresController, type: :controller do
monavis_embed: monavis_embed,
zone_ids: zone_ids,
lien_site_web: lien_site_web,
tags: tags
procedure_tag_names: ['Aao', 'Accompagnement']
}
}
@ -278,21 +279,34 @@ describe Administrateurs::ProceduresController, type: :controller do
end
context 'with specific tag' do
let!(:tags_procedure) { create(:procedure, :published, tags: ['environnement', 'diplomatie']) }
let!(:tag_environnement) { ProcedureTag.create(name: 'environnement') }
let!(:tag_diplomatie) { ProcedureTag.create(name: 'diplomatie') }
let!(:tag_football) { ProcedureTag.create(name: 'football') }
let!(:procedure) do
procedure = create(:procedure, :published)
procedure.procedure_tags << [tag_environnement, tag_diplomatie]
procedure
end
it 'returns procedure who contains at least one tag included in params' do
get :all, params: { tags: ['environnement'] }
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_truthy
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
end
it 'returns procedures who contains all tags included in params' do
get :all, params: { tags: ['environnement', 'diplomatie'] }
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_truthy
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
end
it 'does not returns the procedure' do
it 'returns the procedure when at least one tag is include' do
get :all, params: { tags: ['environnement', 'diplomatie', 'football'] }
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_falsey
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
end
it 'does not return procedure not having the queried tag' do
get :all, params: { tags: ['football'] }
expect(assigns(:procedures)).to be_empty
end
end
@ -495,8 +509,7 @@ describe Administrateurs::ProceduresController, type: :controller do
expect(subject.organisation).to eq(organisation)
expect(subject.administrateurs).to eq([admin])
expect(subject.duree_conservation_dossiers_dans_ds).to eq(duree_conservation_dossiers_dans_ds)
expect(subject.tags).to eq(["planete", "environnement"])
expect(subject.procedure_tags.pluck(:name)).to match_array(['Aao', 'Accompagnement'])
expect(response).to redirect_to(champs_admin_procedure_path(Procedure.last))
expect(flash[:notice]).to be_present
end

View file

@ -58,23 +58,16 @@ describe 'Administrateurs can edit procedures', js: true do
end
context 'when we associate tags' do
scenario 'the administrator can edit and persist the tags' do
procedure.update!(tags: ['social'])
let!(:social_tag) { ProcedureTag.create(name: 'social') }
let!(:planete_tag) { ProcedureTag.create(name: 'planete') }
scenario 'the tags are persisted when not interacting with the tags combobox' do
procedure.procedure_tags << social_tag
visit edit_admin_procedure_path(procedure)
select_combobox('procedure_tags_combo', 'planete', custom_value: true)
click_on 'Enregistrer'
expect(procedure.reload.tags).to eq(['social', 'planete'])
end
scenario 'the tags are persisted when non interacting with the tags combobox' do
procedure.update!(tags: ['social'])
visit edit_admin_procedure_path(procedure)
click_on 'Enregistrer'
expect(procedure.reload.tags).to eq(['social'])
expect(procedure.procedure_tags.pluck(:name)).to match_array(['social'])
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
require "rails_helper"
module Maintenance
RSpec.describe CreateProcedureTagsTask do
describe "#process" do
subject(:process) { described_class.new.process(tag) }
let(:tag) { "Accompagnement" }
let!(:procedure) { create(:procedure, tags: ["Accompagnement"]) }
it "creates the ProcedureTag if it does not exist" do
expect { process }.to change { ProcedureTag.count }.by(1)
expect(ProcedureTag.last.name).to eq(tag)
end
context "when the ProcedureTag already exists" do
let!(:procedure_tag) { ProcedureTag.create(name: tag) }
it "does not create a duplicate ProcedureTag" do
expect { process }.not_to change { ProcedureTag.count }
end
end
it "associates procedures with the ProcedureTag" do
process
expect(procedure.reload.procedure_tags.map(&:name)).to include(tag)
end
end
end
end