Skip to content

Commit 353b934

Browse files
authored
Merge pull request #72 from etalab/features/user-dashboard
Introduce user dashboard with 3 tabs
2 parents f530956 + 1b5e6b0 commit 353b934

File tree

50 files changed

+586
-98
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+586
-98
lines changed

app/assets/stylesheets/dsfr-extensions.css

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
color: var(--text-default-warning);
3333
}
3434

35+
.fr-text-grey {
36+
color: var(--grey-625-425);
37+
}
3538
.fr-bg-alt-blue-france {
3639
background-color: var(--background-action-low-blue-france);
3740
}

app/controllers/authorization_request_forms_controller.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ def create
2727
def show
2828
authorize @authorization_request
2929

30-
if @authorization_request_form.multiple_steps?
30+
if @authorization_request_form.multiple_steps? && @authorization_request.applicant == current_user
3131
redirect_to_current_build_step
32+
elsif @authorization_request_form.multiple_steps?
33+
render 'multiple_steps_as_single_page'
3234
else
3335
render view_path
3436
end
@@ -158,7 +160,7 @@ def authorization_request_params
158160
end
159161

160162
def extract_authorization_request
161-
@authorization_request = authorization_request_class.find(params[:id])
163+
@authorization_request = authorization_request_class.find(params[:id]).decorate
162164
end
163165

164166
def authorization_request_class
+18-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
11
class DashboardController < AuthenticatedUserController
2-
def index; end
2+
layout 'dashboard'
3+
4+
def index
5+
redirect_to dashboard_show_path(id: 'moi')
6+
end
7+
8+
def show
9+
case params[:id]
10+
when 'moi'
11+
@authorization_requests = policy_scope(AuthorizationRequest).where(applicant: current_user)
12+
when 'organisation'
13+
@authorization_requests = policy_scope(AuthorizationRequest)
14+
when 'mentions'
15+
@authorization_requests = AuthorizationRequestsMentionsQuery.new(current_user).perform
16+
else
17+
redirect_to dashboard_show_path(id: 'moi')
18+
end
19+
end
320
end

app/decorators/application_decorator.rb

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
class ApplicationDecorator < Draper::Decorator
2+
delegate :t, to: I18n
3+
4+
delegate :model_name, to: :object
5+
26
# Define methods for all decorated objects.
37
# Helpers are accessed through `helpers` (aka `h`). For example:
48
#
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class AuthorizationRequestDecorator < ApplicationDecorator
2+
delegate_all
3+
4+
def only_in_contacts?(user)
5+
user != object.applicant &&
6+
object.contact_types_for(user).present?
7+
end
8+
9+
def humanized_contact_types_for(user)
10+
object.contact_types_for(user).map do |contact_type|
11+
lookup_i18n_key("#{contact_type}.title").downcase
12+
end
13+
end
14+
15+
private
16+
17+
def lookup_i18n_key(subkey)
18+
t("authorization_request_forms.#{object.model_name.element}.#{subkey}", default: nil) ||
19+
t("authorization_request_forms.default.#{subkey}")
20+
end
21+
end

app/form_builders/authorization_request_form_builder.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def info_for(block)
4040
def contacts_infos(contacts = nil)
4141
return if @template.namespace?(:instruction)
4242

43-
contacts ||= @object.class.contact_types
43+
contacts ||= @object.contact_types
4444

4545
dsfr_accordion(
4646
I18n.t('authorization_request_forms.default.contacts.info.title'),

app/form_builders/dsfr_form_builder.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def hint_for(attribute)
193193
end
194194

195195
def label_value(attribute)
196-
@object.class.human_attribute_name(attribute)
196+
(@object.try(:object) || @object).class.human_attribute_name(attribute)
197197
end
198198

199199
def enhance_input_options(opts)

app/helpers/authorization_requests_helpers.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def authorization_request_model_path(authorization_request)
2424
'#'
2525
elsif authorization_request.new_record?
2626
authorization_request_forms_path(form_uid: authorization_request.form.uid)
27-
elsif authorization_request.form.multiple_steps?
27+
elsif authorization_request.form.multiple_steps? && defined?(wizard_path)
2828
wizard_path
2929
else
3030
authorization_request_form_path(form_uid: authorization_request.form.uid, id: authorization_request.id)
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class CreateAuthorization < ApplicationInteractor
2+
def call
3+
context.authorization = authorization_request.authorizations.create!(
4+
authorization_params
5+
)
6+
end
7+
8+
private
9+
10+
def authorization_request
11+
context.authorization_request
12+
end
13+
14+
def authorization_params
15+
{
16+
data: authorization_request.data,
17+
applicant: authorization_request.applicant,
18+
}
19+
end
20+
end

app/lib/seeds.rb

+36-8
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ def perform
33
create_entities
44

55
create_authorization_request(:api_entreprise)
6-
create_authorization_request(:api_entreprise, :submitted, attributes: { intitule: 'Marché publics', description: very_long_description })
7-
create_authorization_request(:api_particulier, :refused, attributes: { intitule: 'Vente de données personnelles' })
6+
create_authorization_request(:api_entreprise, :submitted, attributes: { intitule: 'Marché publics', description: very_long_description, applicant: another_demandeur })
7+
create_authorization_request(:api_entreprise, :validated, attributes: { intitule: 'Marché publics', contact_technique_email: demandeur.email, applicant: foreign_demandeur })
8+
create_authorization_request(:api_particulier, :refused, attributes: { intitule: 'Vente de données personnelles', applicant: another_demandeur })
89
create_authorization_request(:api_particulier, :changes_requested, attributes: { intitule: 'Tarification cantine' })
910
create_authorization_request(:portail_hubee_demarche_certdc)
1011

@@ -19,25 +20,44 @@ def perform
1920
def flushdb
2021
raise 'Not in production!' if production?
2122

22-
load_all_models!
23+
ActiveRecord::Base.connection.tables.each do |table|
24+
next if table == 'schema_migrations'
2325

24-
ActiveRecord::Base.connection.transaction do
25-
ApplicationRecord.descendants.each(&:delete_all)
26+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{table} CASCADE;")
2627
end
2728
end
2829

2930
private
3031

3132
def create_entities
3233
clamart_organization.users << demandeur
34+
clamart_organization.users << another_demandeur
35+
3336
dinum_organization.users << api_entreprise_instructor
37+
dinum_organization.users << foreign_demandeur
3438
end
3539

3640
def demandeur
3741
@demandeur ||= User.create!(
3842
email: 'user@yopmail.com',
3943
external_id: '1',
40-
current_organization: clamart_organization
44+
current_organization: clamart_organization,
45+
)
46+
end
47+
48+
def another_demandeur
49+
@another_demandeur ||= User.create!(
50+
email: 'user10@yopmail.com',
51+
external_id: '10',
52+
current_organization: clamart_organization,
53+
)
54+
end
55+
56+
def foreign_demandeur
57+
@foreign_demandeur ||= User.create!(
58+
email: 'user11@yopmail.com',
59+
external_id: '11',
60+
current_organization: dinum_organization,
4161
)
4262
end
4363

@@ -72,12 +92,14 @@ def create_authorization_request(kind, status = :draft, attributes: {}, traits:
7292
traits << kind
7393
traits << status
7494

95+
applicant = attributes.delete(:applicant) || demandeur
96+
7597
authorization_request = FactoryBot.create(
7698
:authorization_request,
7799
*traits,
78100
{
79-
applicant: demandeur,
80-
organization: clamart_organization,
101+
applicant:,
102+
organization: applicant.current_organization,
81103
}.merge(attributes)
82104
)
83105

@@ -109,6 +131,12 @@ def create_events_for(authorization_request, status)
109131
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
110132

111133
def create_event(authorization_request, status, attributes = {})
134+
attributes[:user] = if %i[create submit].include?(status)
135+
authorization_request.applicant
136+
else
137+
api_entreprise_instructor
138+
end
139+
112140
FactoryBot.create(
113141
:authorization_request_event,
114142
status,

app/models/authorization.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Authorization < ApplicationRecord
2+
validates :data, presence: true
3+
4+
belongs_to :applicant,
5+
class_name: 'User',
6+
inverse_of: :authorizations_as_applicant
7+
8+
belongs_to :authorization_request
9+
10+
has_one :organization,
11+
through: :authorization_request
12+
end

app/models/authorization_request.rb

+21
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ class AuthorizationRequest < ApplicationRecord
3232
inverse_of: :authorization_request,
3333
dependent: :destroy
3434

35+
has_many :authorizations,
36+
class_name: 'Authorization',
37+
inverse_of: :authorization_request,
38+
dependent: :nullify
39+
40+
has_one :latest_authorization,
41+
-> { order(created_at: :desc).limit(1) },
42+
class_name: 'Authorization',
43+
inverse_of: :authorization_request,
44+
dependent: :nullify
45+
3546
def events
3647
@events ||= AuthorizationRequestEventsQuery.new(self).perform
3748
end
@@ -165,6 +176,16 @@ def finished?
165176
%w[validated refused].include?(state)
166177
end
167178

179+
def contact_types_for(user)
180+
contact_type_key_values = data.select do |key, value|
181+
key =~ /^contact_.*_email$/ && value == user.email
182+
end
183+
184+
contact_type_key_values.keys.map do |key|
185+
key.match(/^(.*)_email$/)[1]
186+
end
187+
end
188+
168189
def applicant_belongs_to_organization
169190
return if organization.blank? || applicant.blank?
170191
return unless applicant.organizations.exclude?(organization)

app/models/authorization_request_event.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@ class AuthorizationRequestEvent < ApplicationRecord
1818

1919
validate :entity_type_is_authorized
2020

21-
# rubocop:disable Metrics/CyclomaticComplexity
21+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
2222
def entity_type_is_authorized
2323
return if name.blank? || entity_type.blank?
2424

2525
return if name == 'refuse' && entity_type == 'DenialOfAuthorization'
2626
return if name == 'request_changes' && entity_type == 'InstructorModificationRequest'
27-
return if entity_type == 'AuthorizationRequest'
27+
return if name == 'approve' && entity_type == 'Authorization'
28+
return if %w[approve refuse request_changes].exclude?(name) && entity_type == 'AuthorizationRequest'
2829

2930
errors.add(:entity_type, :invalid)
3031
end
31-
# rubocop:enable Metrics/CyclomaticComplexity
32+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
3233

3334
def authorization_request
3435
entity.authorization_request

app/models/concerns/authorization_core/contacts.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ def self.contact(kind, validation_condition: nil)
3131
contact_types << kind
3232
end
3333
end
34-
3534
end
3635
end
36+
37+
def contact_types
38+
self.class.contact_types
39+
end
3740
end

app/models/user.rb

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ class User < ApplicationRecord
1313
class_name: 'AuthorizationRequest',
1414
inverse_of: :applicant
1515

16+
has_many :authorizations_as_applicant,
17+
dependent: :restrict_with_exception,
18+
class_name: 'Authorization',
19+
inverse_of: :applicant
20+
1621
def full_name
1722
"#{family_name} #{given_name}"
1823
end
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
class ApproveAuthorizationRequest < ApplicationOrganizer
22
before do
33
context.state_machine_event = :approve
4+
context.event_entity = :authorization
45
end
56

67
organize TriggerAuthorizationRequestEvent,
78
ExecuteAuthorizationRequestBridge,
9+
CreateAuthorization,
810
CreateAuthorizationRequestEventModel,
911
DeliverAuthorizationRequestNotification
1012
end

app/policies/application_policy.rb

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def destroy?
3737
delegate :current_organization, to: :user
3838

3939
class Scope
40+
delegate :current_organization, to: :user
41+
4042
def initialize(user, scope)
4143
@user = user
4244
@scope = scope

app/policies/authorization_request_policy.rb

+20-5
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,34 @@ def new?
55
end
66

77
def show?
8-
same_user?
8+
same_current_organization? ||
9+
current_user_is_contact?
910
end
1011

1112
def update?
12-
same_user? &&
13+
same_user_and_organization? &&
1314
record.in_draft?
1415
end
1516

1617
def submit?
17-
same_user? &&
18+
same_user_and_organization? &&
1819
record.persisted? &&
1920
record.in_draft?
2021
end
2122

2223
private
2324

24-
def same_user?
25+
def same_current_organization?
26+
record.organization == current_organization
27+
end
28+
29+
def same_user_and_organization?
2530
record.applicant == user &&
26-
record.organization == current_organization
31+
same_current_organization?
32+
end
33+
34+
def current_user_is_contact?
35+
record.contact_types_for(user).any?
2736
end
2837

2938
def unicity_constraint_violated?
@@ -35,4 +44,10 @@ def unicity_constraint_violated?
3544
def another_authorization_request_with_same_type_exists?
3645
current_organization.authorization_requests.where(type: record.to_s).any?
3746
end
47+
48+
class Scope < Scope
49+
def resolve
50+
scope.where(organization: current_organization)
51+
end
52+
end
3853
end

0 commit comments

Comments
 (0)