Skip to content

Commit

Permalink
Merge branch 'production' into rdv-plan-calendar-first
Browse files Browse the repository at this point in the history
  • Loading branch information
victormours committed Jan 17, 2025
2 parents 1e9c26e + 2df34bf commit 5e4971f
Show file tree
Hide file tree
Showing 26 changed files with 220 additions and 192 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,6 @@ SCALINGO_APP_ERROR_URL=https://rawcdn.githack.com/betagouv/rdv-service-public/pr
SCALINGO_NO_FRONT_ERROR_URL=https://rawcdn.githack.com/betagouv/rdv-service-public/production/public/maintenance.html
SCALINGO_STOPPED_PAGE_URL=https://rawcdn.githack.com/betagouv/rdv-service-public/production/public/maintenance.html
SCALINGO_TIMEOUT_ERROR_URL=https://rawcdn.githack.com/betagouv/rdv-service-public/production/public/maintenance.html

# Notion
NOTION_API_SECRET=change_me
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ gem "blueprinter"
# Parallel HTTP library on top of libcurl multi.
gem "typhoeus"

# External services
gem "notion-ruby-client", "~> 1.2"

# API documentation

# A Rails Engine that exposes OpenAPI (formerly called Swagger) files as JSON endpoints
Expand Down Expand Up @@ -143,7 +146,7 @@ gem "rubyzip" # zip export files
# Recurring events in Ruby
gem "montrose"
# Supplies TimeOfDay and Shift class
gem "tod", "~> 2.2"
gem "tod"
# A ruby implementation of the iCalendar specification (RFC-5545).
gem "icalendar", "~> 2.5"

Expand Down
16 changes: 14 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ GEM
faraday-net_http (>= 2.0, < 3.2)
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-mashify (0.1.1)
faraday (~> 2.0)
hashie
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (3.1.0)
net-http
ffi (1.16.3)
Expand Down Expand Up @@ -356,6 +361,7 @@ GEM
activesupport (>= 5.2, < 8)
msgpack (1.7.2)
multi_xml (0.6.0)
multipart-post (2.4.1)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
mutex_m (0.3.0)
Expand All @@ -374,6 +380,11 @@ GEM
nokogiri (1.17.1)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
notion-ruby-client (1.2.2)
faraday (>= 2.0)
faraday-mashify (>= 0.1.1)
faraday-multipart (>= 1.0.4)
hashie (~> 5)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
Expand Down Expand Up @@ -663,7 +674,7 @@ GEM
thread_safe (0.3.6)
tilt (2.5.0)
timeout (0.4.1)
tod (2.2.0)
tod (3.1.2)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
Expand Down Expand Up @@ -767,6 +778,7 @@ DEPENDENCIES
lograge
mail
montrose
notion-ruby-client (~> 1.2)
observer
omniauth-github
omniauth-microsoft_graph
Expand Down Expand Up @@ -816,7 +828,7 @@ DEPENDENCIES
sprockets-rails
stackprof
strong_migrations
tod (~> 2.2)
tod
turbolinks (~> 5)
typhoeus
wannabe_bool
Expand Down
59 changes: 0 additions & 59 deletions app/assets/images/franceconnect-bouton-hover.svg

This file was deleted.

59 changes: 0 additions & 59 deletions app/assets/images/franceconnect-bouton.svg

This file was deleted.

13 changes: 0 additions & 13 deletions app/helpers/plage_ouvertures_helper.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
module PlageOuverturesHelper
# Generates ["00:00", "00:05", "00:10", ... "23:50", "23:55"]
EVERY_5_MINUTES_OF_THE_DAY = (0..23).flat_map do |h|
padded_h = format("%02i", h)
(0..55).step(5).map do |m|
padded_min = format("%02i", m)
"#{padded_h}:#{padded_min}"
end
end.freeze

def every_5_minutes_of_the_day
EVERY_5_MINUTES_OF_THE_DAY
end

def display_recurrence(plage_ouverture)
every_part = display_every(plage_ouverture)

Expand Down
2 changes: 1 addition & 1 deletion app/javascript/application_agent_config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require("@rails/ujs").start()
require("turbolinks").start()
import { DsfrNewPassword } from "./components/dsfr-new-password";
import DsfrNewPassword from "./components/dsfr-new-password";
import { Modal } from './components/modal';
import './components/browser-detection';
import 'select2/dist/js/select2.min.js';
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
@import "~bootstrap/scss/mixins";
@import "./_variables";
@import "./_variables_dsfr";
@import "./fontawesome_imports";

// import bootstrap file by file so we can remove unwanted rules component by component
// cf https://github.com/twbs/bootstrap/blob/v4.3.1/scss/bootstrap.scss
@import "~bootstrap/scss/root";
Expand Down
50 changes: 50 additions & 0 deletions app/jobs/cron_job/synchronize_crm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class CronJob::SynchronizeCrm < ApplicationJob
# Base de données "Activation"
# ID récupéré dans l’URL de la page
# Cf: https://developers.notion.com/reference/retrieve-a-database
NOTION_DATABASE_ID = "81a35c7b2490464590a408bbbb78ca2e".freeze

def perform
return unless ENV["NOTION_API_SECRET"]

filter = {
and: [
{
property: "COMPTE PROD",
url: {
contains: instance_hostname,
},
},
],
}

client.database_query(database_id: NOTION_DATABASE_ID, filter:) do |page|
page.results.each do |notion_page|
rdv_count = Rdv.where(organisation: organisations_ids(notion_page.properties["COMPTE PROD"].url)).count
client.update_page(page_id: notion_page.id, properties: { "NOMBRE DE RDV" => rdv_count })
end
end
end

private

def organisations_ids(account_url)
if account_url.match('territories/(\d+)')
territory_id = account_url.match('territories/(\d+)')[1]
territory = Territory.find(territory_id)
Organisation.where(territory: territory).pluck(:id)
elsif account_url.match('organisations/(\d+)')
[account_url.match('organisations/(\d+)')[1]]
else
raise "Unrecognized account URL: #{account_url}"
end
end

def instance_hostname
ENV["HOST"].sub("https://", "")
end

def client
@client ||= Notion::Client.new(token: ENV["NOTION_API_SECRET"])
end
end
12 changes: 6 additions & 6 deletions app/models/concerns/recurrence_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module RecurrenceConcern

included do
serialize :recurrence, coder: Montrose::Recurrence
serialize :start_time, coder: Tod::TimeOfDay
serialize :end_time, coder: Tod::TimeOfDay
attribute :start_time, :time_only # uses Tod::TimeOfDayType
attribute :end_time, :time_only # uses Tod::TimeOfDayType

before_save :clear_empty_recurrence, :set_recurrence_ends_at

Expand All @@ -22,8 +22,8 @@ module RecurrenceConcern
class_methods do
def serialize_for_active_job(record)
manually_serialized_attrs = {
start_time: Tod::TimeOfDay.dump(record.start_time),
end_time: Tod::TimeOfDay.dump(record.end_time),
start_time: record.start_time.to_s,
end_time: record.end_time.to_s,
recurrence: Montrose::Recurrence.dump(record.recurrence),
}
record.attributes.merge(manually_serialized_attrs.stringify_keys)
Expand All @@ -32,8 +32,8 @@ def serialize_for_active_job(record)
def deserialize_for_active_job(hash)
hash = hash.symbolize_keys
manually_deserialized_attrs = {
start_time: Tod::TimeOfDay.load(hash[:start_time]),
end_time: Tod::TimeOfDay.load(hash[:end_time]),
start_time: Tod::TimeOfDay.parse(hash[:start_time]),
end_time: Tod::TimeOfDay.parse(hash[:end_time]),
recurrence: Montrose::Recurrence.load(hash[:recurrence]),
}
new(hash.merge(manually_deserialized_attrs))
Expand Down
2 changes: 2 additions & 0 deletions app/models/rdv_plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def modalite
end
end

private

def return_url_is_authorized
return if return_url.blank?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
p.fr-hint-text Tous les champs sont obligatoires.
= render "devise/shared/dsfr_error_messages", resource: resource
= f.hidden_field :invitation_token
.fr-grid-row.fr-grid-row--gutters
.fr-grid-row.fr-grid-row--gutters.fr-mb-5v
.fr-col-md-6= f.dsfr_text_field :first_name, required: true
.fr-col-md-6= f.dsfr_text_field :last_name, required: true
= render "common/form/new_password_input", f: , hint: "Choisissez votre mot de passe"
Expand Down
2 changes: 0 additions & 2 deletions app/views/admin/users/_responsible_form_fields.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@
.mb-2
div= f.input( \
:notify_by_email, \
include_hidden: false, \
wrapper: false, \
**input_opts.deep_merge(user.confirmed? ? { disabled: true, input_html: { class: "js-force-disabled" } } : {}) \
)
div= f.input( \
:notify_by_sms, \
include_hidden: false, \
wrapper: false, \
**input_opts.deep_merge(user.confirmed? ? { disabled: true, input_html: { class: "js-force-disabled" } } : {}) \
)
Expand Down
4 changes: 2 additions & 2 deletions app/views/common/_recurrence.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
= date_input(f, :end_day)

.row
.col = f.input :start_time, as: :select, collection: every_5_minutes_of_the_day, selected: model.start_time&.strftime("%H:%M"), include_blank: false
.col = f.input :end_time, as: :select, collection: every_5_minutes_of_the_day, selected: model.end_time&.strftime("%H:%M"), include_blank: false
.col = f.input :start_time, as: :time, ignore_date: true, minute_step: 5
.col = f.input :end_time, as: :time, ignore_date: true, minute_step: 5

= f.input :recurrence, as: :hidden, input_html: { value: model.recurrence ? model.recurrence.as_json.to_json : "", "data-target": "recurrence.recurrenceComputed", class: "js-recurrence-computed" }

Expand Down
12 changes: 8 additions & 4 deletions app/views/layouts/_rdv_a_renseigner.html.slim
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/ locals(organisation: nil)
- if current_agent.unknown_past_rdv_count > 0 && !current_agent.conseiller_numerique? # See https://github.com/betagouv/rdv-solidarites.fr/issues/2245
li.list-inline-item.fr-mr-2w
= link_to a_renseigner_admin_organisation_rdvs_path(organisation || current_agent.rdvs.a_renseigner.first.organisation_id), class: "btn text-primary-blue link-dsfr" do
span.fr-icon--sm.fr-mr-1v.fr-icon-warning-fill[aria-hidden="true"]
= "#{current_agent.unknown_past_rdv_count} RDV à renseigner"
/ la condition ci-dessous permet de gérer les cas où le compteur en cache unknown_past_rdv_count
/ est supérieur 0 mais qu’on ne retrouve aucun RDV effectivement à renseigner
- organisation_id = organisation&.id || current_agent.rdvs.a_renseigner.first&.organisation_id
- if organisation_id
li.list-inline-item.fr-mr-2w
= link_to a_renseigner_admin_organisation_rdvs_path(organisation_id), class: "btn text-primary-blue link-dsfr" do
span.fr-icon--sm.fr-mr-1v.fr-icon-warning-fill[aria-hidden="true"]
= "#{current_agent.unknown_past_rdv_count} RDV à renseigner"
6 changes: 3 additions & 3 deletions config/anonymizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,11 @@ tables:
- revoked_at
- expires_in
- table_name: rdv_plans
anonymized_column_names:
- return_url
non_anonymized_column_names:
- duration_in_minutes
- location_type
- return_url
- starts_at
- location_type
- created_at
- updated_at

4 changes: 4 additions & 0 deletions config/initializers/good_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,9 @@
cron: "every day at 10:00 Europe/Paris",
class: "CronJob::WarnAboutExpiringAzureAppSecrets",
},
synchronize_crm: {
cron: "every day at 00:30 Europe/Paris",
class: "CronJob::SynchronizeCrm",
},
}
end
3 changes: 2 additions & 1 deletion config/locales/models/plage_ouverture.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ fr:
first_day:
format: "Le premier jour %{message}"
end_time:
must_be_after_start_time: "doit être après l’heure de début."
format: "%{message}"
must_be_after_start_time: "L’heure de fin doit être après l'heure de début."
lieu:
must_be_enabled: "doit être ouvert."
4 changes: 2 additions & 2 deletions scripts/create_review_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

scalingo --region osc-secnum-fr1 --app demo-rdv-solidarites integration-link-manual-review-app `gh pr view --json number --jq '.number'` &&
gh pr edit -b "$(gh pr view --json body --jq '.body' | sed "1i\\
[Review app](https://demo-rdv-solidarites-pr`gh pr view --json number --jq '.number'`.osc-secnum-fr1.scalingo.io/)
[Review app](https://demo-rdv-solidarites-pr`gh pr view --json number --jq '.number'`.osc-secnum-fr1.scalingo.io/) \\
")"
")"
2 changes: 1 addition & 1 deletion spec/controllers/admin/absences_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
end

describe "DELETE #destroy" do
let!(:absence) { create(:absence, agent_id: agent.id) }
let!(:absence) { create(:absence, :once_a_week, agent_id: agent.id) }

it "destroys the requested absence" do
expect do
Expand Down
18 changes: 18 additions & 0 deletions spec/features/agents/agent_can_crud_plage_ouvertures_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,22 @@
expect(PlageOuverture.last.motifs).to contain_exactly(motif_1_service_avocat, motif_2_service_avocat)
end
end

describe "selecting a time range" do
it "works" do
visit new_admin_organisation_agent_plage_ouverture_path(organisation, agent)
check motif.name
select(lieu.full_name, from: "plage_ouverture_lieu_id")

# Set start time at 10:30
select "10", from: "plage_ouverture_start_time_4i"
select "30", from: "plage_ouverture_start_time_5i"
# Set start time at 13:45
select "13", from: "plage_ouverture_end_time_4i"
select "45", from: "plage_ouverture_end_time_5i"

expect { click_on "Créer la plage d'ouverture" }.to change(PlageOuverture, :count).by(1)
expect(PlageOuverture.last).to have_attributes(start_time: Tod::TimeOfDay.new(10, 30), end_time: Tod::TimeOfDay.new(13, 45))
end
end
end
12 changes: 12 additions & 0 deletions spec/features/agents/users/agent_can_create_user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@
expect(existing_user.reload.organisations).to include(organisation)
end
end

it "permet de désactiver les préférences de notifications mails et SMS" do
fill_in :user_first_name, with: "Marco"
fill_in :user_last_name, with: "Lebreton"
fill_in "Email", with: "marco@lebreton.bzh"
fill_in "Téléphone", with: "0606060606"
uncheck "Accepte les notifications par email"
uncheck "Accepte les notifications par SMS"
click_button "Créer"
expect(find("span", text: /Accepte les notifications par email/).ancestor("li")).to have_content("Désactivées")
expect(find("span", text: /Accepte les notifications par SMS/).ancestor("li")).to have_content("Désactivées")
end
end
24 changes: 24 additions & 0 deletions spec/features/agents/users/agent_can_update_user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,28 @@
expect(current_email.subject).to eq "Vous avez été invité sur RDV Solidarités"
end
end

context "usager n’a pas encore confirmé son compte" do
# lorsqu’un usager a confirmé son compte, l’agent n’a plus la main sur ses préférences de notifs
let!(:user) { create(:user, :unconfirmed, organisations: [organisation]) }

it "permet de désactiver et réactiver les préférences de notifications SMS et email" do
expect(page).to have_content("Modifier l'usager")
expect(page).to have_checked_field("Accepte les notifications par email")
expect(page).to have_checked_field("Accepte les notifications par SMS")
uncheck "Accepte les notifications par email"
uncheck "Accepte les notifications par SMS"
click_button "Enregistrer"
expect(find("span", text: /Accepte les notifications par email/).ancestor("li")).to have_content("Désactivées")
expect(find("span", text: /Accepte les notifications par SMS/).ancestor("li")).to have_content("Désactivées")
within("#spec-primary-user-card") { click_link "Modifier" }
expect(page).to have_unchecked_field("Accepte les notifications par email")
expect(page).to have_unchecked_field("Accepte les notifications par SMS")
check "Accepte les notifications par email"
check "Accepte les notifications par SMS"
click_button "Enregistrer"
expect(find("span", text: /Accepte les notifications par email/).ancestor("li")).to have_content("Activées")
expect(find("span", text: /Accepte les notifications par SMS/).ancestor("li")).to have_content("Activées")
end
end
end
Loading

0 comments on commit 5e4971f

Please sign in to comment.