diff --git a/app/assets/stylesheets/buttons.scss b/app/assets/stylesheets/buttons.scss index 4e399e8ef..c9e3edb6d 100644 --- a/app/assets/stylesheets/buttons.scss +++ b/app/assets/stylesheets/buttons.scss @@ -130,6 +130,11 @@ } } +.icon-size { + width: 18px; + height: 18px; +} + .state-button { display: inline-block; } diff --git a/app/controllers/instructeurs/archives_controller.rb b/app/controllers/instructeurs/archives_controller.rb deleted file mode 100644 index 2917520be..000000000 --- a/app/controllers/instructeurs/archives_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Instructeurs - class ArchivesController < InstructeurController - before_action :ensure_procedure_enabled - - def index - @procedure = procedure - - @archivable_months = archivable_months - @dossiers_termines = @procedure.dossiers.state_termine - @poids_total = ProcedureArchiveService.procedure_files_size(@procedure) - groupe_instructeur = current_instructeur.groupe_instructeurs.where(procedure: @procedure.id).first - @archives = Archive.for_groupe_instructeur(groupe_instructeur) - end - - def create - type = params[:type] - month = Date.strptime(params[:month], '%Y-%m') if params[:month].present? - - archive = ProcedureArchiveService.new(procedure).create_pending_archive(current_instructeur, type, month) - if archive.pending? - ArchiveCreationJob.perform_later(procedure, archive, current_instructeur) - flash[:notice] = "Votre demande a été prise en compte. Selon le nombre de dossiers, cela peut prendre quelques minutes. Vous recevrez un courriel lorsque le fichier sera disponible." - else - flash[:notice] = "Cette archive a déjà été générée." - end - redirect_to instructeur_archives_path(procedure) - end - - private - - def ensure_procedure_enabled - if !procedure.feature_enabled?(:archive_zip_globale) || procedure.brouillon? - flash[:alert] = "L'accès aux archives n'est pas disponible pour cette démarche, merci d'en faire la demande à l'équipe de démarches simplifiees" - return redirect_to instructeur_procedure_path(procedure) - end - end - - def archivable_months - start_date = procedure.published_at.to_date - end_date = Time.zone.now.to_date - - (start_date...end_date) - .map(&:beginning_of_month) - .uniq - .reverse - end - - def procedure - current_instructeur - .procedures - .for_download - .find(params[:procedure_id]) - end - end -end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index e6fb16d21..37b7e1d4c 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -213,6 +213,7 @@ module Instructeurs def telecharger_pjs return head(:forbidden) if !dossier.attachments_downloadable? + generate_pdf_for_instructeur_export files = ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) zipline(files, "dossier-#{dossier.id}.zip") @@ -238,6 +239,12 @@ module Instructeurs .find(params[:dossier_id]) end + def generate_pdf_for_instructeur_export + @include_infos_administration = true + pdf = render_to_string(template: 'dossiers/show', formats: [:pdf]) + dossier.pdf_export_for_instructeur.attach(io: StringIO.open(pdf), filename: "export-#{dossier.id}.pdf", content_type: 'application/pdf') + end + def commentaire_params params.require(:commentaire).permit(:body, :piece_jointe) end diff --git a/app/helpers/archive_helper.rb b/app/helpers/archive_helper.rb deleted file mode 100644 index b349b8793..000000000 --- a/app/helpers/archive_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module ArchiveHelper - def can_generate_archive?(dossiers_termines, poids_total) - dossiers_termines.count < 100 && poids_total < 1.gigabyte - end -end diff --git a/app/javascript/components/ComboMultipleDropdownList.jsx b/app/javascript/components/ComboMultipleDropdownList.jsx index a10c74215..1e25987ec 100644 --- a/app/javascript/components/ComboMultipleDropdownList.jsx +++ b/app/javascript/components/ComboMultipleDropdownList.jsx @@ -241,7 +241,7 @@ function ComboboxToken({ value, ...props }) { onRemove(value); }} > - + Désélectionner {value} diff --git a/app/javascript/components/TypesDeChampEditor/components/MoveButton.jsx b/app/javascript/components/TypesDeChampEditor/components/MoveButton.jsx index 5aa94f983..c8ec442f9 100644 --- a/app/javascript/components/TypesDeChampEditor/components/MoveButton.jsx +++ b/app/javascript/components/TypesDeChampEditor/components/MoveButton.jsx @@ -1,16 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid'; function MoveButton({ isEnabled, icon, title, onClick }) { return ( ); } diff --git a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx index 6dca0c980..00ce93494 100644 --- a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx +++ b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamp.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { sortableElement, sortableHandle } from 'react-sortable-hoc'; import { useInView } from 'react-intersection-observer'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { TrashIcon } from '@heroicons/react/outline'; import DescriptionInput from './DescriptionInput'; import LibelleInput from './LibelleInput'; @@ -77,7 +77,8 @@ const TypeDeChamp = sortableElement( }); }} > - + + Supprimer diff --git a/app/javascript/components/TypesDeChampEditor/components/TypeDeChampRepetitionOptions.jsx b/app/javascript/components/TypesDeChampEditor/components/TypeDeChampRepetitionOptions.jsx index 888d7d3ba..1b8497ff3 100644 --- a/app/javascript/components/TypesDeChampEditor/components/TypeDeChampRepetitionOptions.jsx +++ b/app/javascript/components/TypesDeChampEditor/components/TypeDeChampRepetitionOptions.jsx @@ -1,6 +1,6 @@ import React, { useReducer } from 'react'; import PropTypes from 'prop-types'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { PlusIcon } from '@heroicons/react/outline'; import { SortableContainer, addChampLabel } from '../utils'; import TypeDeChamp from './TypeDeChamp'; @@ -45,7 +45,7 @@ function TypeDeChampRepetitionOptions({ }) } > - +    {addChampLabel(state.isAnnotation)} diff --git a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamps.jsx b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamps.jsx index fa909b5b5..3b7c172cc 100644 --- a/app/javascript/components/TypesDeChampEditor/components/TypeDeChamps.jsx +++ b/app/javascript/components/TypesDeChampEditor/components/TypeDeChamps.jsx @@ -1,6 +1,6 @@ import React, { useReducer } from 'react'; import PropTypes from 'prop-types'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { PlusIcon, ArrowCircleDownIcon } from '@heroicons/react/outline'; import { SortableContainer, addChampLabel } from '../utils'; import TypeDeChamp from './TypeDeChamp'; @@ -38,7 +38,7 @@ function TypeDeChamps({ state: rootState, typeDeChamps }) { {state.typeDeChamps.length === 0 && (

- +   Cliquez sur le bouton «  {addChampLabel(state.isAnnotation)} » pour créer votre premier champ. @@ -56,7 +56,7 @@ function TypeDeChamps({ state: rootState, typeDeChamps }) { }) } > - +    {addChampLabel(state.isAnnotation)} diff --git a/app/javascript/components/TypesDeChampEditor/index.jsx b/app/javascript/components/TypesDeChampEditor/index.jsx index 5e0068f40..d2ec2d494 100644 --- a/app/javascript/components/TypesDeChampEditor/index.jsx +++ b/app/javascript/components/TypesDeChampEditor/index.jsx @@ -1,27 +1,10 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { library } from '@fortawesome/fontawesome-svg-core'; - -import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons/faArrowCircleDown'; -import { faArrowDown } from '@fortawesome/free-solid-svg-icons/faArrowDown'; -import { faArrowsAltV } from '@fortawesome/free-solid-svg-icons/faArrowsAltV'; -import { faArrowUp } from '@fortawesome/free-solid-svg-icons/faArrowUp'; -import { faPlus } from '@fortawesome/free-solid-svg-icons/faPlus'; -import { faTrash } from '@fortawesome/free-solid-svg-icons/faTrash'; import Flash from './Flash'; import OperationsQueue from './OperationsQueue'; import TypeDeChamps from './components/TypeDeChamps'; -library.add( - faArrowCircleDown, - faArrowDown, - faArrowsAltV, - faArrowUp, - faPlus, - faTrash -); - class TypesDeChampEditor extends Component { constructor(props) { super(props); diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index e858bae90..6b59ba733 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -1,7 +1,7 @@ class ApplicationJob < ActiveJob::Base - DEFAULT_MAX_ATTEMPTS_JOBS = 25 + include ActiveJob::RetryOnTransientErrors - retry_on ::Excon::Error::BadRequest + DEFAULT_MAX_ATTEMPTS_JOBS = 25 before_perform do |job| Rails.logger.info("#{job.class.name} started at #{Time.zone.now}") diff --git a/app/jobs/archive_creation_job.rb b/app/jobs/archive_creation_job.rb deleted file mode 100644 index 255af07db..000000000 --- a/app/jobs/archive_creation_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ArchiveCreationJob < ApplicationJob - def perform(procedure, archive, instructeur) - ProcedureArchiveService - .new(procedure) - .collect_files_archive(archive, instructeur) - end -end diff --git a/app/jobs/cron/purge_stale_archives_job.rb b/app/jobs/cron/purge_stale_archives_job.rb deleted file mode 100644 index 4ce48784a..000000000 --- a/app/jobs/cron/purge_stale_archives_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Cron::PurgeStaleArchivesJob < Cron::CronJob - self.schedule_expression = "every 5 minutes" - - def perform - Archive.stale.destroy_all - end -end diff --git a/app/lib/active_job/retry_on_transient_errors.rb b/app/lib/active_job/retry_on_transient_errors.rb new file mode 100644 index 000000000..b3790359c --- /dev/null +++ b/app/lib/active_job/retry_on_transient_errors.rb @@ -0,0 +1,17 @@ +module ActiveJob::RetryOnTransientErrors + extend ActiveSupport::Concern + + TRANSIENT_ERRORS = [ + Excon::Error::InternalServerError, + Excon::Error::GatewayTimeout, + Excon::Error::BadRequest + ] + + included do + if handler_for_rescue(TRANSIENT_ERRORS.first).nil? + TRANSIENT_ERRORS.each do |error_type| + retry_on error_type, attempts: 5, wait: :exponentially_longer + end + end + end +end diff --git a/app/lib/active_storage/downloadable_file.rb b/app/lib/active_storage/downloadable_file.rb index d8ca5cf4c..db86efc73 100644 --- a/app/lib/active_storage/downloadable_file.rb +++ b/app/lib/active_storage/downloadable_file.rb @@ -1,12 +1,14 @@ class ActiveStorage::DownloadableFile def self.create_list_from_dossier(dossier) pjs = PiecesJustificativesService.liste_pieces_justificatives(dossier) - pjs.map do |piece_justificative| + files = pjs.map do |piece_justificative| [ piece_justificative, - "dossier-#{dossier.id}/#{self.timestamped_filename(piece_justificative)}" + self.timestamped_filename(piece_justificative) ] end + files << [dossier.pdf_export_for_instructeur, self.timestamped_filename(dossier.pdf_export_for_instructeur)] + files end private @@ -20,23 +22,19 @@ class ActiveStorage::DownloadableFile timestamp = attachment.created_at.strftime("%d-%m-%Y-%H-%M") id = attachment.id % 10000 - [folder, "#{basename}-#{timestamp}-#{id}#{extension}"].join + "#{folder}/#{basename}-#{timestamp}-#{id}#{extension}" end def self.folder(attachment) - if attachment.name == 'pdf_export_for_instructeur' - return '' - end - case attachment.record_type when 'Dossier' - 'dossier/' + 'dossier' when 'DossierOperationLog', 'BillSignature' - 'horodatage/' + 'horodatage' when 'Commentaire' - 'messagerie/' + 'messagerie' else - 'pieces_justificatives/' + 'pieces_justificatives' end end diff --git a/app/mailers/instructeur_mailer.rb b/app/mailers/instructeur_mailer.rb index 55332cf38..89c58864c 100644 --- a/app/mailers/instructeur_mailer.rb +++ b/app/mailers/instructeur_mailer.rb @@ -1,7 +1,5 @@ # Preview all emails at http://localhost:3000/rails/mailers/instructeur_mailer class InstructeurMailer < ApplicationMailer - helper MailerHelper - layout 'mailers/layout' def user_to_instructeur(email) @@ -44,12 +42,4 @@ class InstructeurMailer < ApplicationMailer mail(to: instructeur.email, subject: subject) end - - def send_archive(instructeur, procedure, archive) - @archive = archive - @procedure = procedure - subject = "Votre archive est disponible" - - mail(to: instructeur.email, subject: subject) - end end diff --git a/app/models/archive.rb b/app/models/archive.rb deleted file mode 100644 index 7ee93720f..000000000 --- a/app/models/archive.rb +++ /dev/null @@ -1,71 +0,0 @@ -# == Schema Information -# -# Table name: archives -# -# id :bigint not null, primary key -# key :text not null -# month :date -# status :string not null -# time_span_type :string not null -# created_at :datetime not null -# updated_at :datetime not null -# -class Archive < ApplicationRecord - include AASM - - RETENTION_DURATION = 1.week - - has_and_belongs_to_many :groupe_instructeurs - - has_one_attached :file - - scope :stale, -> { where('updated_at < ?', (Time.zone.now - RETENTION_DURATION)) } - scope :for_groupe_instructeur, -> (groupe_instructeur) { - joins(:archives_groupe_instructeurs) - .where( - archives_groupe_instructeurs: { groupe_instructeur: groupe_instructeur } - ) - } - - enum time_span_type: { - everything: 'everything', - monthly: 'monthly' - } - - enum status: { - pending: 'pending', - generated: 'generated' - } - - aasm whiny_persistence: true, column: :status, enum: true do - state :pending, initial: true - state :generated - - event :make_available do - transitions from: :pending, to: :generated - end - end - - def available? - status == 'generated' && file.attached? - end - - def filename(procedure) - if time_span_type == 'everything' - "procedure-#{procedure.id}.zip" - else - "procedure-#{procedure.id}-mois-#{I18n.l(month, format: '%Y-%m')}.zip" - end - end - - def self.find_or_create_archive(time_span_type, month, groupe_instructeurs) - create_with(groupe_instructeurs: groupe_instructeurs) - .create_or_find_by(time_span_type: time_span_type, month: month, key: generate_cache_key(groupe_instructeurs)) - end - - private - - def self.generate_cache_key(groupe_instructeurs) - groupe_instructeurs.map(&:id).sort.join('-') - end -end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 8b8a1e60e..a0add0355 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -172,12 +172,7 @@ class Dossier < ApplicationRecord scope :en_construction, -> { not_archived.state_en_construction } scope :en_instruction, -> { not_archived.state_en_instruction } scope :termine, -> { not_archived.state_termine } - scope :processed_in_month, -> (month) do - state_termine - .joins(:traitements) - .where(traitements: { processed_at: month.beginning_of_month..month.end_of_month }) - end - scope :downloadable_sorted, -> { + scope :downloadable_sorted, -> { state_not_brouillon .includes( :user, diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index d3e2fc21b..f43eda8fb 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -25,7 +25,6 @@ class Instructeur < ApplicationRecord has_many :followed_dossiers, through: :follows, source: :dossier has_many :previously_followed_dossiers, -> { distinct }, through: :previous_follows, source: :dossier has_many :trusted_device_tokens, dependent: :destroy - has_many :archives has_one :user, dependent: :nullify diff --git a/app/models/procedure.rb b/app/models/procedure.rb index b4760c353..ea0b4e570 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -156,20 +156,6 @@ class Procedure < ApplicationRecord includes(:draft_revision, :published_revision, administrateurs: :user) } - scope :for_download, -> { - includes( - :groupe_instructeurs, - dossiers: { - champs: [ - piece_justificative_file_attachment: :blob, - champs: [ - piece_justificative_file_attachment: :blob - ] - ] - } - ) - } - validates :libelle, presence: true, allow_blank: false, allow_nil: false validates :description, presence: true, allow_blank: false, allow_nil: false validates :administrateurs, presence: true diff --git a/app/services/pieces_justificatives_service.rb b/app/services/pieces_justificatives_service.rb index f3cd8303c..a5903531b 100644 --- a/app/services/pieces_justificatives_service.rb +++ b/app/services/pieces_justificatives_service.rb @@ -1,11 +1,10 @@ class PiecesJustificativesService def self.liste_pieces_justificatives(dossier) - dossier_export = generate_dossier_export(dossier) pjs_champs = pjs_for_champs(dossier) pjs_commentaires = pjs_for_commentaires(dossier) pjs_dossier = pjs_for_dossier(dossier) - ([dossier_export] + pjs_champs + pjs_commentaires + pjs_dossier) + (pjs_champs + pjs_commentaires + pjs_dossier) .filter(&:attached?) end @@ -44,17 +43,6 @@ class PiecesJustificativesService private - def self.generate_dossier_export(dossier) - pdf = ApplicationController - .render(template: 'dossiers/show', formats: [:pdf], - assigns: { - include_infos_administration: true, - dossier: dossier - }) - dossier.pdf_export_for_instructeur.attach(io: StringIO.open(pdf), filename: "export-#{dossier.id}.pdf", content_type: 'application/pdf') - dossier.pdf_export_for_instructeur - end - def self.pjs_for_champs(dossier) allowed_champs = dossier.champs + dossier.champs_private diff --git a/app/services/procedure_archive_service.rb b/app/services/procedure_archive_service.rb deleted file mode 100644 index b26757210..000000000 --- a/app/services/procedure_archive_service.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'tempfile' - -class ProcedureArchiveService - def initialize(procedure) - @procedure = procedure - end - - def create_pending_archive(instructeur, type, month = nil) - groupe_instructeurs = instructeur - .groupe_instructeurs - .where(procedure: @procedure) - - Archive.find_or_create_archive(type, month, groupe_instructeurs) - end - - def collect_files_archive(archive, instructeur) - if archive.time_span_type == 'everything' - dossiers = @procedure.dossiers.state_termine - else - dossiers = @procedure.dossiers.processed_in_month(archive.month) - end - - files = create_list_of_attachments(dossiers) - - tmp_file = Tempfile.new(['tc', '.zip']) - - Zip::OutputStream.open(tmp_file) do |zipfile| - files.each do |attachment, pj_filename| - zipfile.put_next_entry(pj_filename) - zipfile.puts(attachment.download) - end - end - - archive.file.attach(io: File.open(tmp_file), filename: archive.filename(@procedure)) - tmp_file.delete - archive.make_available! - InstructeurMailer.send_archive(instructeur, @procedure, archive).deliver_later - end - - def self.procedure_files_size(procedure) - dossiers_files_size(procedure.dossiers) - end - - def self.dossiers_files_size(dossiers) - dossiers.map do |dossier| - liste_pieces_justificatives_for_archive(dossier).sum(&:byte_size) - end.sum - end - - private - - def create_list_of_attachments(dossiers) - dossiers.flat_map do |dossier| - ActiveStorage::DownloadableFile.create_list_from_dossier(dossier) - end - end - - def self.attachments_from_champs_piece_justificative(champs) - champs - .filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:piece_justificative) } - .filter { |pj| pj.piece_justificative_file.attached? } - .map(&:piece_justificative_file) - end - - def self.liste_pieces_justificatives_for_archive(dossier) - champs_blocs_repetables = dossier.champs - .filter { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } - .flat_map(&:champs) - - attachments_from_champs_piece_justificative(champs_blocs_repetables + dossier.champs) - end -end diff --git a/app/views/instructeur_mailer/send_archive.html.haml b/app/views/instructeur_mailer/send_archive.html.haml deleted file mode 100644 index 11857c69f..000000000 --- a/app/views/instructeur_mailer/send_archive.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -- content_for(:title, 'Votre archive est disponible') - -%p - Bonjour, - -%p - Votre archive pour la démarche - = link_to("#{@procedure.id} − #{@procedure.libelle}", instructeur_procedure_url(@procedure.id)) - est disponible. - Vous pouvez la télécharger dans votre espace de gestion des archives. - -%p - = round_button('Consulter mes archives', instructeur_archives_url(@procedure), :primary) - -%p - Ce fichier est - %b valide une semaine - et peut-être téléchargé - %b plusieurs fois. - -= render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/archives/create.js.haml b/app/views/instructeurs/archives/create.js.haml deleted file mode 100644 index 7fe9f7f0b..000000000 --- a/app/views/instructeurs/archives/create.js.haml +++ /dev/null @@ -1 +0,0 @@ -= render_flash(sticky: true) diff --git a/app/views/instructeurs/archives/index.html.haml b/app/views/instructeurs/archives/index.html.haml deleted file mode 100644 index 82ec92f1d..000000000 --- a/app/views/instructeurs/archives/index.html.haml +++ /dev/null @@ -1,91 +0,0 @@ -- content_for(:title, "Archives pour #{@procedure.libelle}") - -= render partial: 'new_administrateur/breadcrumbs', - locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)), - 'Archives'] } - -.container - %h1 Archives - - .card.featured - .card-title Gestion de vos archives - %p - Vous pouvez télécharger les archives des dossiers terminés depuis la publication de la procédure au format Zip. - - %p - Cet export contient les demande déposée par l'usager et la liste des pièces justificatives transmises. - - %p - Cet export n'est pas possible pour le moment pour les démarches à forte volumétrie. - Nous vous invitons à regarder - = link_to 'la documentation', ARCHIVAGE_DOC_URL - afin de voir les options à votre disposition pour mettre en place un système d'archive. - - %table.table.hoverable - %thead - %tr - %th   - %th Nombre de dossiers terminés - %th Poids estimé - %th Télécharger - - %tbody - - if can_generate_archive?(@dossiers_termines, @poids_total) - %tr - - matching_archive = @archives.find_by(time_span_type: 'everything') - %td - Tous les dossiers - %td - = @dossiers_termines.count - %td - - if matching_archive.present? && matching_archive.available? - - weight = matching_archive.file.byte_size - - else - - weight = @poids_total - = number_to_human_size(weight) - %td - - if matching_archive.try(&:available?) - = link_to url_for(matching_archive.file), class: 'button primary' do - %span.icon.download-white - = t(:archive_ready_html, generated_period: time_ago_in_words(matching_archive.updated_at), scope: [:instructeurs, :procedure]) - - elsif matching_archive.try(&:pending?) - %span.icon.retry - = t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure]) - - elsif @dossiers_termines.count > 0 - = link_to instructeur_archives_path(@procedure, type: 'everything'), method: :post, class: "button" do - %span.icon.new-folder - Demander la création - - else - Rien à télécharger ! - - @archivable_months.each do |month| - - dossiers_termines = @procedure.dossiers.processed_in_month(month) - - nb_dossiers_termines = dossiers_termines.count - - matching_archive = @archives.find_by(time_span_type: 'monthly', month: month) - %tr - %td - = I18n.l(month, format: "%B %Y") - %td - = nb_dossiers_termines - %td - - if matching_archive.present? && matching_archive.available? - - weight = matching_archive.file.byte_size - - else - - weight = ProcedureArchiveService::dossiers_files_size(dossiers_termines) - = number_to_human_size(weight) - %td - - if nb_dossiers_termines > 0 - - if matching_archive.present? - - if matching_archive.status == 'generated' && matching_archive.file.attached? - = link_to url_for(matching_archive.file), class: 'button primary' do - %span.icon.download-white - = t(:archive_ready_html, generated_period: time_ago_in_words(matching_archive.updated_at), scope: [:instructeurs, :procedure]) - - else - %span.icon.retry - = t(:archive_pending_html, created_period: time_ago_in_words(matching_archive.created_at), scope: [:instructeurs, :procedure]) - - else - = link_to instructeur_archives_path(@procedure, type:'monthly', month: month.strftime('%Y-%m')), method: :post, class: "button" do - %span.icon.new-folder - Démander la création - - else - Rien à télécharger ! - diff --git a/app/views/instructeurs/procedures/_download_dossiers.html.haml b/app/views/instructeurs/procedures/_download_dossiers.html.haml index b3be64c10..a465eb9c9 100644 --- a/app/views/instructeurs/procedures/_download_dossiers.html.haml +++ b/app/views/instructeurs/procedures/_download_dossiers.html.haml @@ -16,6 +16,3 @@ - else %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, no_progress_notification: true) } = t(:export_pending_html, export_time: time_ago_in_words(export.created_at), export_format: ".#{format}", scope: [:instructeurs, :procedure]) - - if procedure.feature_enabled?(:archive_zip_globale) - %li - = link_to t(:download_archive, scope: [:instructeurs, :procedure]), instructeur_archives_path(procedure) diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 45d529258..dfa8c8582 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -14,6 +14,12 @@ ActiveSupport.on_load(:active_storage_attachment) do include AttachmentVirusScannerConcern end +Rails.application.reloader.to_prepare do + class ActiveStorage::BaseJob + include ActiveJob::RetryOnTransientErrors + end +end + # When an OpenStack service is initialized it makes a request to fetch # `publicURL` to use for all operations. We intercept the method that reads # this url and replace the host with DS_Proxy host. This way all the operation diff --git a/config/locales/views/instructeurs/fr.yml b/config/locales/views/instructeurs/fr.yml index ef2b1987b..3cc5fabdc 100644 --- a/config/locales/views/instructeurs/fr.yml +++ b/config/locales/views/instructeurs/fr.yml @@ -8,6 +8,3 @@ fr: ods_html: Demander un export au format .ods export_ready_html: Télécharger l’export au format %{export_format}
(généré il y a %{export_time}) export_pending_html: Un export au format %{export_format} est en train d’être généré
(demandé il y a %{export_time}) - download_archive: Télécharger une archive au format .zip de tous les dossiers et leurs pièces jointes - archive_pending_html: Archive en cours de création
(demandée il y a %{created_period}) - archive_ready_html: Télécharger l'archive
(demandée il y a %{generated_period}) diff --git a/config/routes.rb b/config/routes.rb index acdac88ea..bb953ffa4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -383,8 +383,6 @@ Rails.application.routes.draw do get 'telecharger_pjs' => 'dossiers#telecharger_pjs' end end - - resources :archives, only: [:index, :create, :show], controller: 'archives' end end get "recherche" => "recherche#index" diff --git a/db/migrate/20201104163658_create_archive_for_groupe_instructeur.rb b/db/migrate/20201104163658_create_archive_for_groupe_instructeur.rb deleted file mode 100644 index 51793681c..000000000 --- a/db/migrate/20201104163658_create_archive_for_groupe_instructeur.rb +++ /dev/null @@ -1,17 +0,0 @@ -class CreateArchiveForGroupeInstructeur < ActiveRecord::Migration[6.0] - def change - create_table :archives do |t| - t.string :status, null: false - t.date :month - t.string :content_type, null: false - t.timestamps - end - - create_table "archives_groupe_instructeurs", force: :cascade do |t| - t.belongs_to :archive, foreign_key: true, null: false - t.belongs_to :groupe_instructeur, foreign_key: true, null: false - - t.timestamps - end - end -end diff --git a/db/migrate/20210427112642_rename_content_type_to_to_time_span_type_for_archives.rb b/db/migrate/20210427112642_rename_content_type_to_to_time_span_type_for_archives.rb deleted file mode 100644 index be6d5a404..000000000 --- a/db/migrate/20210427112642_rename_content_type_to_to_time_span_type_for_archives.rb +++ /dev/null @@ -1,5 +0,0 @@ -class RenameContentTypeToToTimeSpanTypeForArchives < ActiveRecord::Migration[6.1] - def change - rename_column :archives, :content_type, :time_span_type - end -end diff --git a/db/migrate/20210427124500_add_key_to_archives.rb b/db/migrate/20210427124500_add_key_to_archives.rb deleted file mode 100644 index 08559a0dc..000000000 --- a/db/migrate/20210427124500_add_key_to_archives.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddKeyToArchives < ActiveRecord::Migration[6.1] - def change - add_column :archives, :key, :text, null: false - add_index :archives, [:key, :time_span_type, :month], unique: true - end -end diff --git a/db/schema.rb b/db/schema.rb index eefe0112f..956e5a302 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_04_27_124500) do +ActiveRecord::Schema.define(version: 2021_04_27_120002) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -81,25 +81,6 @@ ActiveRecord::Schema.define(version: 2021_04_27_124500) do t.index ["procedure_id"], name: "index_administrateurs_procedures_on_procedure_id" end - create_table "archives", force: :cascade do |t| - t.string "status", null: false - t.date "month" - t.string "time_span_type", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.text "key", null: false - t.index ["key", "time_span_type", "month"], name: "index_archives_on_key_and_time_span_type_and_month", unique: true - end - - create_table "archives_groupe_instructeurs", force: :cascade do |t| - t.bigint "archive_id", null: false - t.bigint "groupe_instructeur_id", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["archive_id"], name: "index_archives_groupe_instructeurs_on_archive_id" - t.index ["groupe_instructeur_id"], name: "index_archives_groupe_instructeurs_on_groupe_instructeur_id" - end - create_table "assign_tos", id: :serial, force: :cascade do |t| t.integer "instructeur_id" t.datetime "created_at" @@ -754,8 +735,6 @@ ActiveRecord::Schema.define(version: 2021_04_27_124500) do end add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" - add_foreign_key "archives_groupe_instructeurs", "archives" - add_foreign_key "archives_groupe_instructeurs", "groupe_instructeurs" add_foreign_key "assign_tos", "groupe_instructeurs" add_foreign_key "attestation_templates", "procedures" add_foreign_key "attestations", "dossiers" diff --git a/package.json b/package.json index d825b40e0..102d3b750 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,6 @@ { "dependencies": { "@babel/preset-react": "^7.12.13", - "@fortawesome/fontawesome-svg-core": "^1.2.34", - "@fortawesome/free-solid-svg-icons": "^5.15.2", - "@fortawesome/react-fontawesome": "^0.1.14", "@heroicons/react": "^1.0.1", "@mapbox/mapbox-gl-draw": "^1.2.2", "@rails/actiontext": "^6.0.3", diff --git a/spec/controllers/instructeurs/archives_controller_spec.rb b/spec/controllers/instructeurs/archives_controller_spec.rb deleted file mode 100644 index 7ce7e4d22..000000000 --- a/spec/controllers/instructeurs/archives_controller_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -describe Instructeurs::ArchivesController, type: :controller do - let(:procedure1) { create(:procedure, :published, groupe_instructeurs: [gi1]) } - let(:procedure2) { create(:procedure, :published, groupe_instructeurs: [gi2]) } - let!(:instructeur) { create(:instructeur, groupe_instructeurs: [gi1, gi2]) } - let!(:archive1) { create(:archive, :generated, groupe_instructeurs: [gi1]) } - let!(:archive2) { create(:archive, :generated, groupe_instructeurs: [gi2]) } - let(:gi1) { create(:groupe_instructeur) } - let(:gi2) { create(:groupe_instructeur) } - - before do - sign_in(instructeur.user) - Flipper.enable(:archive_zip_globale, procedure1) - end - - after { Timecop.return } - - describe '#index' do - before do - create_dossier_for_month(procedure1, 2021, 3) - create_dossier_for_month(procedure1, 2021, 3) - create_dossier_for_month(procedure1, 2021, 2) - Timecop.freeze(Time.zone.local(2021, 3, 5)) - end - - it 'displays archives' do - get :index, { params: { procedure_id: procedure1.id } } - - expect(assigns(:dossiers_termines).size).to eq(3) - expect(assigns(:archives)).to eq([archive1]) - end - end - - describe '#create' do - let(:month) { '21-03' } - let(:date_month) { Date.strptime(month, "%Y-%m") } - let(:archive) { create(:archive) } - let(:subject) do - post :create, { - params: { procedure_id: procedure1.id, type: 'monthly', month: month } - } - end - - it "performs archive creation job" do - allow_any_instance_of(ProcedureArchiveService).to receive(:create_pending_archive).and_return(archive) - expect { subject }.to have_enqueued_job(ArchiveCreationJob).with(procedure1, archive, instructeur) - expect(flash.notice).to include("Votre demande a été prise en compte") - end - end - - private - - def create_dossier_for_month(procedure, year, month) - Timecop.freeze(Time.zone.local(year, month, 5)) - create(:dossier, :accepte, :with_attestation, procedure: procedure) - end -end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index ab6d9ff5a..b31cb81c0 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -712,6 +712,13 @@ describe Instructeurs::DossiersController, type: :controller do dossier_id: dossier.id } end + + context 'when zip download is disabled through flipflop' do + it 'is forbidden' do + subject + expect(response).to have_http_status(:forbidden) + end + end end describe "#delete_dossier" do diff --git a/spec/factories/archive.rb b/spec/factories/archive.rb deleted file mode 100644 index 41fbc2542..000000000 --- a/spec/factories/archive.rb +++ /dev/null @@ -1,15 +0,0 @@ -FactoryBot.define do - factory :archive do - time_span_type { 'everything' } - groupe_instructeurs { [association(:groupe_instructeur)] } - key { 'unique-key' } - - trait :pending do - status { 'pending' } - end - - trait :generated do - status { 'generated' } - end - end -end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index a384687b6..d361b3cf3 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -12,8 +12,6 @@ feature 'Inviting an expert:', js: true do context 'as an Instructeur' do scenario 'I can invite an expert' do - allow(ClamavService).to receive(:safe_file?).and_return(true) - # assign instructeur to linked dossier instructeur.assign_to_procedure(linked_dossier.procedure) diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index 257ca2a73..e24f3c48c 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -165,10 +165,10 @@ feature 'Instructing a dossier:', js: true do expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip" expect(files.size).to be 3 - expect(files[0].filename.include?('export')).to be_truthy - expect(files[1].filename.include?('piece_justificative_0')).to be_truthy - expect(files[1].uncompressed_size).to be File.size(path) - expect(files[2].filename.include?('horodatage/operation')).to be_truthy + expect(files[0].filename.include?('piece_justificative_0')).to be_truthy + expect(files[0].uncompressed_size).to be File.size(path) + expect(files[1].filename.include?('horodatage/operation')).to be_truthy + expect(files[2].filename.include?('dossier/export')).to be_truthy end scenario 'A instructeur can download an archive containing several identical attachments' do @@ -180,13 +180,13 @@ feature 'Instructing a dossier:', js: true do expect(DownloadHelpers.download).to include "dossier-#{dossier.id}.zip" expect(files.size).to be 4 - expect(files[0].filename.include?('export')).to be_truthy + expect(files[0].filename.include?('piece_justificative_0')).to be_truthy expect(files[1].filename.include?('piece_justificative_0')).to be_truthy - expect(files[2].filename.include?('piece_justificative_0')).to be_truthy - expect(files[1].filename).not_to eq files[2].filename + expect(files[0].filename).not_to eq files[1].filename + expect(files[0].uncompressed_size).to be File.size(path) expect(files[1].uncompressed_size).to be File.size(path) - expect(files[2].uncompressed_size).to be File.size(path) - expect(files[3].filename.include?('horodatage/operation')).to be_truthy + expect(files[2].filename.include?('horodatage/operation')).to be_truthy + expect(files[3].filename.include?('dossier/export')).to be_truthy end before { DownloadHelpers.clear_downloads } diff --git a/spec/jobs/active_storage/base_job_spec.rb b/spec/jobs/active_storage/base_job_spec.rb new file mode 100644 index 000000000..2a565623b --- /dev/null +++ b/spec/jobs/active_storage/base_job_spec.rb @@ -0,0 +1,3 @@ +describe ActiveStorage::BaseJob do + it_behaves_like 'a job retrying transient errors' +end diff --git a/spec/jobs/application_job_spec.rb b/spec/jobs/application_job_spec.rb index 8de03c541..e2d00b7fb 100644 --- a/spec/jobs/application_job_spec.rb +++ b/spec/jobs/application_job_spec.rb @@ -1,6 +1,8 @@ include ActiveJob::TestHelper RSpec.describe ApplicationJob, type: :job do + it_behaves_like 'a job retrying transient errors' + describe 'perform' do before do allow(Rails.logger).to receive(:info) @@ -13,23 +15,7 @@ RSpec.describe ApplicationJob, type: :job do end end - context 'when ::Excon::Error::BadRequest is raised' do - # https://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html#method-i-retry_on - # retry on will try 5 times and then bubble up the error - it 'makes 5 attempts' do - assert_performed_jobs 5 do - ExconErrJob.perform_later rescue ::Excon::Error::BadRequest - end - end - end - class ChildJob < ApplicationJob def perform; end end - - class ExconErrJob < ApplicationJob - def perform - raise ::Excon::Error::BadRequest.new('bad request') - end - end end diff --git a/spec/lib/active_job/retry_on_transient_errors_spec.rb b/spec/lib/active_job/retry_on_transient_errors_spec.rb new file mode 100644 index 000000000..b1ee46ccd --- /dev/null +++ b/spec/lib/active_job/retry_on_transient_errors_spec.rb @@ -0,0 +1,9 @@ +describe ActiveJob::RetryOnTransientErrors do + # rubocop:disable Rails/ApplicationJob + class Job < ActiveJob::Base + include ActiveJob::RetryOnTransientErrors + end + # rubocop:enable Rails/ApplicationJob + + it_behaves_like 'a job retrying transient errors', Job +end diff --git a/spec/models/archive_spec.rb b/spec/models/archive_spec.rb deleted file mode 100644 index 5753340d3..000000000 --- a/spec/models/archive_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -describe Dossier do - include ActiveJob::TestHelper - - before { Timecop.freeze(Time.zone.now) } - after { Timecop.return } - - let(:archive) { create(:archive) } - - describe 'scopes' do - describe 'staled' do - let(:recent_archive) { create(:archive) } - let(:staled_archive) { create(:archive, updated_at: (Archive::RETENTION_DURATION + 2).days.ago) } - - subject do - archive; recent_archive; staled_archive - Archive.stale - end - - it { is_expected.to match_array([staled_archive]) } - end - end - - describe '.status' do - it { expect(archive.status).to eq('pending') } - end - - describe '#make_available!' do - before { archive.make_available! } - it { expect(archive.status).to eq('generated') } - end - - describe '#available?' do - subject { archive.available? } - context 'without attachment' do - let(:archive) { create(:archive, file: nil) } - it { is_expected.to eq(false) } - end - - context 'with an attachment' do - context 'when the attachment was created but the process was not over' do - let(:archive) { create(:archive, :pending, file: Rack::Test::UploadedFile.new('spec/fixtures/files/file.pdf', 'application/pdf')) } - it { is_expected.to eq(false) } - end - - context 'when the attachment was created but the process was not over' do - let(:archive) { create(:archive, :generated, file: Rack::Test::UploadedFile.new('spec/fixtures/files/file.pdf', 'application/pdf')) } - it { is_expected.to eq(true) } - end - end - end -end diff --git a/spec/services/pieces_justificatives_service_spec.rb b/spec/services/pieces_justificatives_service_spec.rb index fc151f908..d0ad55499 100644 --- a/spec/services/pieces_justificatives_service_spec.rb +++ b/spec/services/pieces_justificatives_service_spec.rb @@ -16,12 +16,7 @@ describe PiecesJustificativesService do # to be exported it 'ensures no titre identite is given' do expect(champ_identite.piece_justificative_file).to be_attached - expect(subject.any? { |piece| piece.name == 'piece_justificative_file' }).to be_falsy - end - - it 'returns export pdf of the dossier' do - expect(champ_identite.piece_justificative_file).to be_attached - expect(subject.any? { |piece| piece.name == 'pdf_export_for_instructeur' }).to be_truthy + expect(subject).to eq([]) end end end diff --git a/spec/services/procedure_archive_service_spec.rb b/spec/services/procedure_archive_service_spec.rb deleted file mode 100644 index 80e013f74..000000000 --- a/spec/services/procedure_archive_service_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -describe ProcedureArchiveService do - let(:procedure) { create(:procedure, :published) } - let(:instructeur) { create(:instructeur) } - let(:service) { ProcedureArchiveService.new(procedure) } - let(:year) { 2020 } - let(:month) { 3 } - let(:date_month) { Date.strptime("#{year}-#{month}", "%Y-%m") } - describe '#create_pending_archive' do - context 'for a specific month' do - it 'creates a pending archive' do - archive = service.create_pending_archive(instructeur, 'monthly', date_month) - - expect(archive.time_span_type).to eq 'monthly' - expect(archive.month).to eq date_month - expect(archive.pending?).to be_truthy - end - end - - context 'for all months' do - it 'creates a pending archive' do - archive = service.create_pending_archive(instructeur, 'everything') - - expect(archive.time_span_type).to eq 'everything' - expect(archive.month).to eq nil - expect(archive.pending?).to be_truthy - end - end - end - - describe '#collect_files_archive' do - before do - create_dossier_for_month(year, month) - create_dossier_for_month(2020, month) - end - - after { Timecop.return } - - context 'for a specific month' do - let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month) } - let(:year) { 2021 } - let(:mailer) { double('mailer', deliver_later: true) } - - it 'collect files' do - expect(InstructeurMailer).to receive(:send_archive).and_return(mailer) - - service.collect_files_archive(archive, instructeur) - - archive.file.open do |f| - files = ZipTricks::FileReader.read_zip_structure(io: f) - expect(files.size).to be 2 - expect(files.first.filename).to include("export") - expect(files.last.filename).to include("attestation") - end - expect(archive.file.attached?).to be_truthy - end - end - - context 'for all months' do - let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending') } - let(:mailer) { double('mailer', deliver_later: true) } - - it 'collect files' do - expect(InstructeurMailer).to receive(:send_archive).and_return(mailer) - - service.collect_files_archive(archive, instructeur) - - archive = Archive.last - archive.file.open do |f| - files = ZipTricks::FileReader.read_zip_structure(io: f) - expect(files.size).to be 4 - end - expect(archive.file.attached?).to be_truthy - end - end - end - - private - - def create_dossier_for_month(year, month) - Timecop.freeze(Time.zone.local(year, month, 5)) - create(:dossier, :accepte, :with_attestation, procedure: procedure) - end -end diff --git a/spec/support/shared_examples_for_jobs.rb b/spec/support/shared_examples_for_jobs.rb new file mode 100644 index 000000000..f30a67769 --- /dev/null +++ b/spec/support/shared_examples_for_jobs.rb @@ -0,0 +1,29 @@ +RSpec.shared_examples 'a job retrying transient errors' do |job_class = described_class| + context 'when a transient network error is raised' do + ExconErrorJob = Class.new(job_class) do + def perform + raise Excon::Error::InternalServerError, 'msg' + end + end + + it 'makes 5 attempts before raising the exception up' do + assert_performed_jobs 5 do + ExconErrorJob.perform_later rescue Excon::Error::InternalServerError + end + end + end + + context 'when another type of error is raised' do + StandardErrorJob = Class.new(job_class) do + def perform + raise StandardError + end + end + + it 'makes only 1 attempt before raising the exception up' do + assert_performed_jobs 1 do + StandardErrorJob.perform_later rescue StandardError + end + end + end +end diff --git a/spec/views/instructeur/procedures/_download_dossiers.html.haml_spec.rb b/spec/views/instructeur/procedures/_download_dossiers.html.haml_spec.rb index 8fb8f346e..947ce178f 100644 --- a/spec/views/instructeur/procedures/_download_dossiers.html.haml_spec.rb +++ b/spec/views/instructeur/procedures/_download_dossiers.html.haml_spec.rb @@ -12,15 +12,5 @@ describe 'instructeurs/procedures/_download_dossiers.html.haml', type: :view do context "when procedure has at least 1 dossier" do let(:dossier_count) { 1 } it { is_expected.to include("Télécharger tous les dossiers") } - - context "With zip archive enabled" do - before { Flipper.enable(:archive_zip_globale, procedure) } - it { is_expected.to include("Télécharger une archive au format .zip") } - end - - context "With zip archive disabled" do - before { Flipper.disable(:archive_zip_globale, procedure) } - it { is_expected.not_to include("Télécharger une archive au format .zip") } - end end end diff --git a/yarn.lock b/yarn.lock index c653dddfe..322c51cbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1005,32 +1005,6 @@ enabled "2.0.x" kuler "^2.0.0" -"@fortawesome/fontawesome-common-types@^0.2.34": - version "0.2.34" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz#0a8c348bb23b7b760030f5b1d912e582be4ec915" - integrity sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA== - -"@fortawesome/fontawesome-svg-core@^1.2.34": - version "1.2.34" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz#1d1a7c92537cbc2b8a83eef6b6d824b4b5b46b26" - integrity sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg== - dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.34" - -"@fortawesome/free-solid-svg-icons@^5.15.2": - version "5.15.2" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.2.tgz#25bb035de57cf85aee8072965732368ccc8e8943" - integrity sha512-ZfCU+QjaFsdNZmOGmfqEWhzI3JOe37x5dF4kz9GeXvKn/sTxhqMtZ7mh3lBf76SvcYY5/GKFuyG7p1r4iWMQqw== - dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.34" - -"@fortawesome/react-fontawesome@^0.1.14": - version "0.1.14" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz#bf28875c3935b69ce2dc620e1060b217a47f64ca" - integrity sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA== - dependencies: - prop-types "^15.7.2" - "@heroicons/react@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.1.tgz#66d25f6441920bd5c2146ea27fd33995885452dd"