Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
14 changes: 14 additions & 0 deletions app/assets/stylesheets/components/data_provider_card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.data-provider-card-img {
background: white;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
min-height: 9rem;
}

.data-provider-card-img img {
max-height: 6rem;
max-width: 80%;
object-fit: contain;
}
9 changes: 9 additions & 0 deletions app/components/instructor_menu_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
</a>
</li>

<% if show_definitions %>
<li class="fr-nav__item">
<a class="fr-nav__link center" href="<%= instruction_data_providers_path %>" target="_self">
<%= t('layouts.header.menu.instruction.formulaires') %>
</a>
</li>
<% end %>

<% if show_drafts %>
<li class="fr-nav__item">
<a class="fr-nav__link center" href="<%= instruction_instructor_draft_requests_path %>" target="_self">
Expand All @@ -27,3 +35,4 @@
</a>
</li>
<% end %>

7 changes: 4 additions & 3 deletions app/components/instructor_menu_component.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
class InstructorMenuComponent < ApplicationComponent
def initialize(show_drafts:, show_templates:, show_user_rights:)
def initialize(show_drafts:, show_templates:, show_user_rights:, show_definitions: false)
@show_drafts = show_drafts
@show_templates = show_templates
@show_user_rights = show_user_rights
@show_definitions = show_definitions
end

def render?
@show_drafts || @show_templates || @show_user_rights
@show_drafts || @show_templates || @show_user_rights || @show_definitions
end

attr_reader :show_drafts, :show_templates, :show_user_rights
attr_reader :show_drafts, :show_templates, :show_user_rights, :show_definitions
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="fr-card fr-enlarge-link">
<div class="fr-card__body">
<div class="fr-card__content">
<h2 class="fr-card__title">
<%= link_to name, '#' %>
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
</h2>
<p class="fr-card__desc fr-text--xs">
<code><%= slug %></code>
</p>
<div class="fr-card__end">
<p class="fr-card__detail fr-icon-file-text-line">
<%= t('instruction.data_providers.card.formulaires_count', count: definitions_count) %>
</p>
</div>
</div>
</div>
<% if logo.attached? %>
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
<div class="fr-card__header">
<div class="fr-card__img data-provider-card-img">
<%= image_tag logo, alt: name %>
</div>
</div>
<% end %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Molecules::Instruction::DataProviders::CardComponent < ApplicationComponent
def initialize(data_provider:, definitions_count:)
@data_provider = data_provider
@definitions_count = definitions_count
end

private

attr_reader :data_provider, :definitions_count

delegate :name, :slug, :logo, to: :data_provider
end
13 changes: 13 additions & 0 deletions app/controllers/instruction/data_providers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Instruction::DataProvidersController < InstructionController
def index
authorize %i[instruction data_provider], :index?
@data_providers = policy_scope([:instruction, DataProvider]).with_attached_logo.order(:name)
@definitions_counts = @data_providers.index_with { |data_provider| reporter_definitions_count(data_provider) }
end

private

def reporter_definitions_count(data_provider)
data_provider.authorization_definitions.count { |ad| current_user.reporter?(ad.id) }
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
end
end
2 changes: 1 addition & 1 deletion app/models/data_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def self.preload_linked_habilitation_types!(data_providers)

def authorization_definitions
@authorization_definitions ||= AuthorizationDefinition.all.select do |authorization_definition|
authorization_definition.provider&.slug == slug
authorization_definition.provider_slug == slug
end
end

Expand Down
4 changes: 4 additions & 0 deletions app/models/role_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def covers?(definition_id = nil)

delegate :any?, to: :@roles

def provider_slugs
@roles.filter_map(&:provider_slug).uniq
end

def definition_ids
@definition_ids ||= @roles.flat_map { |parsed|
if parsed.fd_level?
Expand Down
13 changes: 13 additions & 0 deletions app/policies/instruction/data_provider_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Instruction::DataProviderPolicy < ApplicationPolicy
class Scope < Scope
def resolve
return scope.all if user.admin?

scope.where(slug: user.roles_for(:reporter).provider_slugs)
end
end

def index?
user.reporter? || user.manager?
end
end
2 changes: 2 additions & 0 deletions app/services/skip_links_implemented_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class SkipLinksImplementedChecker
instruction/user_rights#edit
instruction/user_rights#update

instruction/data_providers#index

admin#index
admin/user_rights#index
admin/user_rights#new
Expand Down
15 changes: 15 additions & 0 deletions app/views/instruction/data_providers/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% set_title! t('page_titles.instruction_data_providers') %>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Le titre n'est toujours pas le bon, il devrait être "Fournisseur de Données - Instruction - DataPass".
Exactement comme on a fait pour
authorization_request_form_wizard: "%{step_name} - %{definition_name} - %{authorization_request_name}" par exemple.

Ce n'est pas un affichage de "formulaires" ici.
De plus c'est le rôle du fil d'ariane d'exposer aux lecteurs d'écran des utilisateurs ou se trouve l'utilisateur via <nav aria-label="vous êtes ici"

Ici, il nous manque un fil d'ariane et le titre reste à changer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Je ferait le fil d'ariane dans une autre PR quand j'aurais toute la suite de pages à gérer, c'est prévu.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Je me suis déjà exprimé sur le nom de cette page, et la confusion autour du titre. Je vais pas faire une modif pour un truc que je vais jeter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Si tu jettes la PR tu la close, c'est dans la liste à review.

Et mon commentaire sera de toute façon pertinent pour l'autre page, si on affiche une liste de fournisseurs de données et que tu as quand même un titre qui dit formulaires, on serait quand même en NC

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Non je veux pas la close, ma PR qui suit se base dessus

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Elle se merge pas dans develop, mais dans une branche qui résumera toute l'avancée sur la feature d'affichage des Formulaires et Cas d'usages. Donc selon moi on peut la merge et empiler la suite dessus, et il y en aura une dans la chaîne qui servira à retaper ce lien de "Formulaires" en une vraie liste de formulaires.


<h1 class="fr-my-5w"><%= t('.title') %></h1>

<% if @data_providers.empty? %>
<p class="fr-text--lg"><%= t('.empty') %></p>
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
<% else %>
<div class="fr-grid-row fr-grid-row--gutters">
<% @data_providers.each do |data_provider| %>
<div class="fr-col-12 fr-col-md-6 fr-col-lg-4">
<%= render Molecules::Instruction::DataProviders::CardComponent.new(data_provider:, definitions_count: @definitions_counts[data_provider]) %>
</div>
<% end %>
</div>
<% end %>
3 changes: 2 additions & 1 deletion app/views/layouts/header/_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<%= render InstructorMenuComponent.new(
show_drafts: policy([:instruction, :instructor_draft_request]).enabled?,
show_templates: policy([:instruction, :message_template]).index?,
show_user_rights: policy([:instruction, :user_right]).index?
show_user_rights: policy([:instruction, :user_right]).index?,
show_definitions: policy([:instruction, :data_provider]).index?
) %>
<% end %>
</ul>
Expand Down
1 change: 1 addition & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fr:
authorizations_and_requests: Demandes / habilitations
message_templates: Modèles de message
user_rights: Gestion des droits
formulaires: Formulaires
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
footer:
tagline: L'outil de gestion des habilitations juridiques pour les données à accès restreint.
external_links:
Expand Down
9 changes: 9 additions & 0 deletions config/locales/instruction.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ fr:
edit_templates_link: modifier les modèles
email_preview_accordion:
title: Voir un aperçu de l'email
data_providers:
index:
title: Fournisseurs de données
empty: Aucun fournisseur de données disponible.
card:
formulaires_count:
zero: Aucun formulaire
one: 1 formulaire
other: "%{count} formulaires"
dashboard:
authorization_requests:
search:
Expand Down
1 change: 1 addition & 0 deletions config/locales/page_titles.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fr:
admin_user_organization_verifications: Vérification lien utilisateur / organisation

instruction_dashboard: Tableau de bord instructeur
instruction_data_providers: Formulaires
Comment thread
JeSuisUnCaillou marked this conversation as resolved.
instruction_show: "Instruction %{definition_name} - %{authorization_request_name}"
instruction_initiated_requests: Demandes initiées par les instructeurs
instruction_draft_requests_new: Initier une demande d’habilitation
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
namespace :instruction do
get '/tableau-de-bord/:id', to: 'dashboard#show', as: :dashboard_show

resources :data_providers, only: [:index], path: 'fournisseurs-donnees'

resources :message_templates, only: %i[index new create edit update destroy], path: 'modeles-messages'

resources :authorization_requests, only: %w[show], path: 'demandes' do
Expand Down
115 changes: 115 additions & 0 deletions docs/shaping/display formulaires/1. Explore prototype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Context
Comment thread
JeSuisUnCaillou marked this conversation as resolved.

Read this PR : https://github.com/etalab/data_pass/pull/1595/
This was a prototype of rough display of the formulaires (definitions) and cas d'usages (forms).

Then read this PR : https://github.com/etalab/data_pass/pull/1607.
This was built over the previous PR, and is a prototype of pretty display of the formulaires and cas d'usages.

Now my plan is to start over from develop to make these features, and I'll use the inspiration of this design : /home/caillou/Downloads/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone).html

But first, we are going to explore the code of the prototypes.

# Explore the prototypes

Explore the 2 PRs and tell me what should be reconsidered in the controllers and models to make it clean and respect the datapass' guidelines.

> Response was a very detailed review, but actually I first need a summary of what was done to the models

## Actually

Actually, I first need a summary of the PR's changes to the models

## Summary of the model changes

> Claude's answer

All model changes are in PR 1595 only — PR 1607 touched no models. Here's the summary:

---

### New model: `FormTemplate`

A new ActiveRecord model backed by the `form_templates` table. It sits between `HabilitationType` (DB) and `AuthorizationRequestForm` (façade).

**Schema:** `slug`, `name`, `description`, `introduction`, `use_case`, `default`, `public`, `startable_by_applicant`, `single_page_view`, `service_provider_id` (string, FK to YAML-backed `ServiceProvider`), plus jsonb columns `steps`, `static_blocks`, `scopes_config`, `initialize_with`.

**Key behaviors:**
- `friendly_id` on `name`, `paper_trail` versioning
- `belongs_to :habilitation_type`
- Validates that the slug doesn't collide with any YAML-defined form uid (`slug_not_taken_by_yaml`)
- Enforces exactly one `default: true` per `HabilitationType` (via `only_one_default_per_habilitation_type`, `ht_keeps_at_least_one_default`, and `ensure_not_last_default` on `before_destroy`)
- `after_commit :reset_arf_cache` — invalidates the `AuthorizationRequestForm` in-memory cache (via Redis) whenever a template is created, updated, or destroyed
- `service_provider` is resolved at read time via `ServiceProvider.find(service_provider_id)`, returning `nil` gracefully if not found

---

### Modified: `HabilitationType`

- Added `has_many :form_templates, dependent: :destroy`
- Added `after_create :ensure_default_form_template!` — when a new `HabilitationType` is created, it immediately creates one default `FormTemplate` with slug `"#{slug}-default"`, seeding it with empty jsonb columns (steps/scopes cascade from the HT at render time)
- `after_save :reset_static_caches` and `after_destroy :reset_static_caches` replaced by a single `after_commit :reset_static_caches, on: %i[create update destroy]` — avoids firing before the transaction is committed

---

### Modified: `AuthorizationRequestForm` (the YAML/DB façade)

The façade previously iterated `HabilitationType` directly; it now iterates `FormTemplate`.

- `db_records` now does `FormTemplate.includes(:habilitation_type).filter_map { build_form_from_template(template) }` instead of building one form per `HabilitationType`. This means one `HabilitationType` can now produce **N forms** (one per `FormTemplate`) rather than always exactly one.
- `build_form_from_template` maps all `FormTemplate` columns to form attributes, with cascading fallback to `HabilitationType` for `name`, `description`, `introduction`, and `steps` when the template columns are blank.
- New attribute `form_template` exposed on the façade to carry the originating `FormTemplate` record.
- `inherited?(field)` — returns `true` if the given field (`name`, `description`, `introduction`, `steps`) is blank on the underlying `FormTemplate` (i.e. it fell back to the `HabilitationType` value).
- `from_database?` — returns `true` if the form came from a `FormTemplate` (vs a YAML record).


---

## We dont' want the in-db cas d'usages (form_templates) yet.

Ok, so I understand the PR 1595 introduces a in-db model for the cas d'usages (form_templates), but I don't want to do that yet. The work I want to do is to _only_ display formulaires (definitions) and cas d'usages (forms) for the instructors and managers.

Can you summarize the model changes left if we ignore this ? (I think there should be none). Then summarize the controllers changes if we ignore this.

## Model changes without `FormTemplate`

> Claude's answer

None. Every model change in PR 1595 exists purely to introduce and support `FormTemplate`:
- `HabilitationType` changes (`has_many :form_templates`, `after_create :ensure_default_form_template!`, callback timing fix) are all in service of `FormTemplate`
- `AuthorizationRequestForm` changes (`build_form_from_template`, `inherited?`, `from_database?`, `form_template` attribute) are all wiring `FormTemplate` into the façade

The existing `AuthorizationDefinition` + `AuthorizationRequestForm` (YAML-backed) are already sufficient to display formulaires and cas d'usages. Zero model changes needed.

---

## Controller changes, ignoring `FormTemplate`

> Claude's answer

Everything still applies — the controllers only read `AuthorizationDefinition` and `AuthorizationRequestForm`, neither of which requires `FormTemplate`.

**From PR 1595 (new infrastructure):**
- `Instruction::DataProvidersController` — `index` listing providers the current user has reporter access to
- `Instruction::AbstractCatalogueController` — `before_action :set_data_provider` (find by slug + authorize), shared parent for the two below
- `Instruction::FormulairesController` — `index` filtering `authorization_definitions` by policy, `show` loading `available_forms` and counts
- `Instruction::CasUsagesController` — `show` finding a specific form by uid within a formulaire
- 3 Pundit policies (`DataProviderPolicy`, `AuthorizationDefinitionPolicy`, `AuthorizationRequestFormPolicy`)
- Routes: `/instruction/fournisseurs-donnees` + nested formulaires + cas-usage

**From PR 1607 (visual prototype built on top):**
- `FormulairesController#show` extended with: `@authorization_request` stub (for WYSIWYG preview), stats counts, email count
- `CasUsagesController#index` added, `show` extended with stats counts
- `Instruction::EmailsAutomatiquesController` + `AutomaticEmailsCatalog` service — entirely independent of `FormTemplate`
- `DashboardController` — navigation shortcut to the provider/formulaire list
- Various views, partials, CSS, locales


# Let's go

We are ready to implement the display of definitions (formulaires) and forms (cas d'usages).

Understand that in French and in the UI, we are naming authorization_definitions "Formulaires" and forms "Cas d'usages". And we don't rename them in the codebase yet.

In the following prompts, I'll call "the prototype" the code in the 2 Pull Request #1595 and #1607 (mostly 1607).

39 changes: 39 additions & 0 deletions docs/shaping/display formulaires/2. List data providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

> Read `/docs/shaping/display_formulaires/Explore prototype.md` for context.

# List data providers and their authorization_definitions

First, we are going to make the routes, controllers and views to list the data providers.
We will also add the button "Gérer mes formulaires" in /instruction/tableau-de-bord/demandes

Use the prototype as an example for the UI and the wordings, but you can rethink all the code.

Make ViewComponents like describe in @CLAUDE.md:133-141

## Submenu entry instead of button in dashboard

Instead of the button in the dashboard, I want a new submenu entry called "Formulaires", like in this design : file:///home/caillou/Apps/datagouv/data_pass/docs/shaping/display%20formulaires/designs/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone)%20(V0)%20(1).html

(the deisgn call it "Formulaires et cas d'usages", but we will just name it "Formulaires").

## A page to list the providers only

Ok, forget about the list of definitions, we will just list the providers. I want the design to look like this : https://sandbox.datapass.api.gouv.fr/instruction/fournisseurs-donnees (which is the PR #1607 you should have take as a model actually)

## Tweaks

- We are missing the cursor:pointer on hover, and the blue arrow bottom right. Check out #1607 for that.

- We are not renaming authorization_definitions into formulaires yet ! Don't use "formulaire" where we are actually referencing authorization_definitions in the code.

- Don't use inlince css, make a class and put the css in a stylesheet.

- rename accessible_count into definitions_count in the card component

- I moved the feature file under /features/instructeurs/gestion_des_formulaires/

- the view component preview without logo has an error : Couldn't find DataProvider with [WHERE "logo_attachment"."id" IS NULL]. It should instead render a card without logo.

- why does the view component preview doesn't look like what we see on /instruction/fournisseurs-donnees ? The logo is not centered, and overflows from its container.

- Add a feature to check if an instructor with only 2 data_providers in his roles sees only these two providers in the list, and another to check if an instructor with no data_providers in his roles but one definition, sees the provider of this definition.
Loading