Merge pull request #4827 from betagouv/dev

2020-02-25-01
This commit is contained in:
Pierre de La Morinerie 2020-02-25 13:56:51 +01:00 committed by GitHub
commit 526e54ca17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 210 additions and 115 deletions

View file

@ -138,7 +138,7 @@ GEM
selenium-webdriver
case_transform (0.2)
activesupport
chartkick (3.3.0)
chartkick (3.3.1)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chunky_png (1.3.11)
@ -381,7 +381,7 @@ GEM
nenv (0.3.0)
netrc (0.11.0)
nio4r (2.5.2)
nokogiri (1.10.7)
nokogiri (1.10.8)
mini_portile2 (~> 2.4.0)
notiffany (0.1.1)
nenv (~> 0.1)

View file

@ -73,7 +73,7 @@
.procedure-form__options-summary {
cursor: pointer;
.header-section {
.header-subsection {
display: inline-block;
}
}

View file

@ -42,9 +42,19 @@ class Users::ConfirmationsController < Devise::ConfirmationsController
if sign_in_after_confirmation?(resource)
resource.remember_me = true
sign_in(resource)
end
if procedure_from_params
commencer_path(path: procedure_from_params.path)
elsif signed_in?
# Will try to use `stored_location_for` to find a path
after_sign_in_path_for(resource_name)
else
super(resource_name, resource)
end
end
def procedure_from_params
params[:procedure_id] && Procedure.find_by(id: params[:procedure_id])
end
end

View file

@ -21,6 +21,12 @@ class Users::RegistrationsController < Devise::RegistrationsController
# POST /resource
def create
# We may need the confirmation mailer to access the current procedure.
# But there's no easy way to pass an argument to the mailer through
# all Devise code.
# So instead we use a per-request global variable.
CurrentConfirmation.procedure_after_confirmation = @procedure
# Handle existing user trying to sign up again
existing_user = User.find_by(email: params[:user][:email])
if existing_user.present?

View file

@ -21,8 +21,8 @@ module ProcedureHelper
def types_de_champ_data(procedure)
{
isAnnotation: false,
typeDeChampsTypes: types_de_champ_types,
typeDeChamps: types_de_champ_as_json(procedure.types_de_champ),
typeDeChampsTypes: TypeDeChamp.type_de_champ_types_for(procedure, current_user),
typeDeChamps: procedure.types_de_champ.as_json_for_editor,
baseUrl: procedure_types_de_champ_path(procedure),
directUploadUrl: rails_direct_uploads_url
}
@ -31,58 +31,12 @@ module ProcedureHelper
def types_de_champ_private_data(procedure)
{
isAnnotation: true,
typeDeChampsTypes: types_de_champ_types,
typeDeChamps: types_de_champ_as_json(procedure.types_de_champ_private),
typeDeChampsTypes: TypeDeChamp.type_de_champ_types_for(procedure, current_user),
typeDeChamps: procedure.types_de_champ_private.as_json_for_editor,
baseUrl: procedure_types_de_champ_path(procedure),
directUploadUrl: rails_direct_uploads_url
}
end
private
TOGGLES = {
TypeDeChamp.type_champs.fetch(:integer_number) => :administrateur_champ_integer_number
}
def types_de_champ_types
types_de_champ_types = TypeDeChamp.type_de_champs_list_fr
types_de_champ_types.select! do |tdc|
toggle = TOGGLES[tdc.last]
toggle.blank? || feature_enabled?(toggle)
end
types_de_champ_types
end
TYPES_DE_CHAMP_BASE = {
except: [
:created_at,
:options,
:order_place,
:parent_id,
:private,
:procedure_id,
:stable_id,
:type,
:updated_at
],
methods: [
:cadastres,
:drop_down_list_value,
:parcelles_agricoles,
:piece_justificative_template_filename,
:piece_justificative_template_url,
:quartiers_prioritaires
]
}
TYPES_DE_CHAMP = TYPES_DE_CHAMP_BASE
.merge(include: { types_de_champ: TYPES_DE_CHAMP_BASE })
def types_de_champ_as_json(types_de_champ)
types_de_champ.includes(:drop_down_list,
piece_justificative_template_attachment: :blob,
types_de_champ: [:drop_down_list, piece_justificative_template_attachment: :blob])
.as_json(TYPES_DE_CHAMP)
end
end

View file

@ -1,8 +1,14 @@
import Chartkick from 'chartkick';
import { toggle } from '@utils';
import Highcharts from 'highcharts';
import { toggle, delegate } from '@utils';
export function toggleChart(event, chartClass) {
export default function() {
return null;
}
function toggleChart(event) {
const nextSelectorItem = event.target,
chartClass = event.target.dataset.toggleChart,
nextChart = document.querySelector(chartClass),
nextChartId = nextChart.children[0].id,
currentSelectorItem = nextSelectorItem.parentElement.querySelector(
@ -23,3 +29,8 @@ export function toggleChart(event, chartClass) {
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
Chartkick.charts[nextChartId].getChartObject().reflow();
}
delegate('click', '[data-toggle-chart]', toggleChart);
Chartkick.use(Highcharts);
window.Chartkick = Chartkick;

View file

@ -0,0 +1,3 @@
import Loadable from '../components/Loadable';
export default Loadable(() => import('../components/Chartkick'));

View file

@ -4,8 +4,6 @@ import Rails from '@rails/ujs';
import * as ActiveStorage from '@rails/activestorage';
import '@rails/actiontext';
import 'whatwg-fetch'; // window.fetch polyfill
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import ReactRailsUJS from 'react_ujs';
import '../shared/page-update-event';
@ -37,7 +35,6 @@ import {
motivationCancel,
showImportJustificatif
} from '../new_design/state-button';
import { toggleChart } from '../new_design/toggle-chart';
import { replaceSemicolonByComma } from '../new_design/avis';
import {
acceptEmailSuggestion,
@ -52,21 +49,18 @@ const DS = {
showMotivation,
motivationCancel,
showImportJustificatif,
toggleChart,
replaceSemicolonByComma,
acceptEmailSuggestion,
discardEmailSuggestionBox
};
// Start Rails helpers
Chartkick.addAdapter(Highcharts);
Rails.start();
Turbolinks.start();
ActiveStorage.start();
// Expose globals
window.DS = window.DS || DS;
window.Chartkick = Chartkick;
// (Both Rails redirects and ReactRailsUJS expect Turbolinks to be globally available)
window.Turbolinks = Turbolinks;

View file

@ -1,9 +1,10 @@
// Include runtime-polyfills for older browsers.
// Due to babel.config.js's 'useBuiltIns', only polyfills actually
// required by the browsers we support will be included.
import '@babel/polyfill';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'dom4';
import 'intersection-observer';
import './polyfills/insertAdjacentElement';
import './polyfills/dataset';
import('intersection-observer');

View file

@ -15,6 +15,7 @@ class DeviseUserMailer < Devise::Mailer
def confirmation_instructions(record, token, opts = {})
opts[:from] = NO_REPLY_EMAIL
@procedure = CurrentConfirmation.procedure_after_confirmation || nil
super
end
end

View file

@ -0,0 +1,3 @@
class CurrentConfirmation < ActiveSupport::CurrentAttributes
attribute :procedure_after_confirmation
end

View file

@ -25,7 +25,9 @@ class Export < ApplicationRecord
file.attach(
io: io,
filename: filename,
content_type: content_type
content_type: content_type,
# We generate the exports ourselves, so they are safe
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end

View file

@ -115,10 +115,6 @@ class TypeDeChamp < ApplicationRecord
dynamic_type.build_champ
end
def self.type_de_champs_list_fr
type_champs.map { |champ| [I18n.t("activerecord.attributes.type_de_champ.type_champs.#{champ.last}"), champ.first] }
end
def check_mandatory
if non_fillable?
self.mandatory = false
@ -162,6 +158,10 @@ class TypeDeChamp < ApplicationRecord
type_champ == TypeDeChamp.type_champs.fetch(:dossier_link)
end
def legacy_number?
type_champ == TypeDeChamp.type_champs.fetch(:number)
end
def public?
!private?
end
@ -194,6 +194,54 @@ class TypeDeChamp < ApplicationRecord
GraphQL::Schema::UniqueWithinType.encode('Champ', stable_id)
end
FEATURE_FLAGS = {}
def self.type_de_champ_types_for(procedure, user)
has_legacy_number = (procedure.types_de_champ + procedure.types_de_champ_private).any?(&:legacy_number?)
type_champs.map do |type_champ|
[I18n.t("activerecord.attributes.type_de_champ.type_champs.#{type_champ.last}"), type_champ.first]
end.filter do |tdc|
if tdc.last == TypeDeChamp.type_champs.fetch(:number)
has_legacy_number
else
feature_name = FEATURE_FLAGS[tdc.last]
feature_name.blank? || Flipper.enabled?(feature_name, user)
end
end
end
TYPES_DE_CHAMP_BASE = {
except: [
:created_at,
:options,
:order_place,
:parent_id,
:private,
:procedure_id,
:stable_id,
:type,
:updated_at
],
methods: [
:cadastres,
:drop_down_list_value,
:parcelles_agricoles,
:piece_justificative_template_filename,
:piece_justificative_template_url,
:quartiers_prioritaires
]
}
TYPES_DE_CHAMP = TYPES_DE_CHAMP_BASE
.merge(include: { types_de_champ: TYPES_DE_CHAMP_BASE })
def self.as_json_for_editor
includes(:drop_down_list,
piece_justificative_template_attachment: :blob,
types_de_champ: [:drop_down_list, piece_justificative_template_attachment: :blob])
.as_json(TYPES_DE_CHAMP)
end
private
def set_default_drop_down_list

View file

@ -7,7 +7,8 @@
%p
Pour activer votre compte sur demarches-simplifiees.fr, veuillez cliquer sur le lien suivant :
= link_to(confirmation_url(@user, confirmation_token: @token), confirmation_url(@user, confirmation_token: @token))
- link = confirmation_url(@user, confirmation_token: @token, procedure_id: @procedure&.id)
= link_to(link, link)
- else
- content_for(:title, "Changement d'adresse email")

View file

@ -4,12 +4,14 @@
Télécharger tous les dossiers
.dropdown-content.fade-in-down{ style: 'width: 330px' }
%ul.dropdown-items
- [[xlsx_export, :xlsx], [csv_export, :csv], [ods_export, :ods]].each do |(export, format)|
- [[xlsx_export, :xlsx], [ods_export, :ods], [csv_export, :csv]].each do |(export, format)|
%li
- if export.nil?
= link_to "Demander un export au format .#{format}", download_export_instructeur_procedure_path(procedure, export_format: format), remote: true
- export_text = "Demander un export au format .#{format}"
- if format == :csv
- export_text = "Demander un export au format .#{format}<br/>(uniquement les dossiers, sans les champs répétables)".html_safe
= link_to export_text, download_export_instructeur_procedure_path(procedure, export_format: format), remote: true
- elsif export.ready?
= link_to "Télécharger l'export au format .#{format}", export.file.service_url, target: "_blank", rel: "noopener"
- else
%span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, no_progress_notification: true) }
L'export au format .#{format} est en cours de préparation

View file

@ -6,6 +6,10 @@
'Statistiques'] }
.statistiques
-# Load Chartkick lazily, by using our React lazy-loader.
-# (Chartkick itself doesn't use React though)
= react_component('Chartkick')
%h1.new-h1= title
.stat-cards
- if @usual_traitement_time.present?
@ -29,4 +33,3 @@
.chart
- colors = %w(#C3D9FF #0069CC #1C7EC9) # from _colors.scss
= pie_chart @termines_states, colors: colors

View file

@ -13,7 +13,7 @@
%span.mandatory *
= f.text_area :description, rows: '6', placeholder: 'Description de la démarche, destinataires, etc. ', class: 'form-control'
%h2.header-section Logo de la démarche
%h3.header-subsection Logo de la démarche
= render 'shared/attachment/edit',
{ form: f,
attached_file: @procedure.logo,
@ -21,7 +21,7 @@
user_can_destroy: true }
- if !@procedure.locked?
%h2.header-section Conservation des données
%h3.header-subsection Conservation des données
= f.label :duree_conservation_dossiers_dans_ds do
Sur demarches-simplifiees.fr
%span.mandatory *
@ -39,7 +39,7 @@
Où les usagers trouveront-ils le lien vers la démarche ?
= f.text_field :lien_site_web, class: 'form-control', placeholder: 'https://exemple.gouv.fr/ma_demarche'
%h2.header-section
%h3.header-subsection
Cadre juridique
%span.mandatory *
@ -64,7 +64,7 @@
attached_file: @procedure.deliberation,
user_can_destroy: true }
%h2.header-section Notice explicative de la démarche
%h3.header-subsection Notice explicative de la démarche
%p.notice
Une notice explicative est un document destiné à guider lusager dans sa démarche. Cest un document que vous avez élaboré et qui peut prendre la forme dun fichier doc, dun pdf ou encore de diapositives. Le bouton pour télécharger cette notice apparaît en haut du formulaire pour lusager.
@ -77,7 +77,7 @@
user_can_destroy: true }
- if !@procedure.locked?
%h2.header-section À qui sadresse ma démarche ?
%h3.header-subsection À qui sadresse ma démarche ?
.radios.vertical
= f.label :for_individual, value: true do
= f.radio_button :for_individual, true
@ -95,7 +95,7 @@
%details.procedure-form__options-details
%summary.procedure-form__options-summary
%h2.header-section Options avancées
%h3.header-subsection Options avancées
- if feature_enabled?(:administrateur_web_hook)
= f.label :web_hook_url do
@ -115,10 +115,10 @@
= f.label :declarative_with_state do
Démarche déclarative
= f.select :declarative_with_state, Procedure.declarative_attributes_for_select, { include_blank: true }, class: 'form-control'
= f.select :declarative_with_state, Procedure.declarative_attributes_for_select, { prompt: 'Non' }, class: 'form-control'
%p.explication
Par défaut, une démarche n'est pas déclarative; à son dépot, un dossier est «en construction». Vous pouvez choisir de la rendre déclarative, afin que tous les dossiers déposés soient immédiatement au statut "en instruction" en "accepté".
Par défaut, une démarche nest pas déclarative ; à son dépôt, un dossier est « en construction ». Vous pouvez choisir de la rendre déclarative, afin que tous les dossiers déposés passent immédiatement au statut « en instruction » ou « accepté ».
%br
%br
Dans le cadre d'une démarche déclarative, au dépot, seul l'email associé à l'état choisi est envoyé. (ex: démarche déclarative «accepté»: envoi uniquement de l'email d'acceptation)
Dans le cadre dune démarche déclarative, au dépôt, seul lemail associé à létat choisi est envoyé. (ex: démarche déclarative « accepté » : envoi uniquement de lemail d'acceptation)

View file

@ -1,6 +1,9 @@
- content_for(:title, 'Statistiques')
.statistiques
-# Load Chartkick lazily, by using our React lazy-loader.
-# (Chartkick itself doesn't use React though)
= react_component('Chartkick')
%h1.new-h1 Statistiques
@ -55,9 +58,9 @@
.stat-card.stat-card-half.pull-left
%ul.segmented-control.pull-right
%li.segmented-control-item.segmented-control-item-active{ :onclick => "DS.toggleChart(event, '.monthly-procedures-chart');" }
%li.segmented-control-item.segmented-control-item-active{ data: { 'toggle-chart': '.monthly-procedures-chart' } }
Par mois
%li.segmented-control-item{ :onclick => "DS.toggleChart(event, '.cumulative-procedures-chart');" }
%li.segmented-control-item{ data: { 'toggle-chart': '.cumulative-procedures-chart' } }
Cumul
%span.stat-card-title.pull-left Démarches dématérialisées
.clearfix
@ -70,9 +73,9 @@
.stat-card.stat-card-half.pull-left
%ul.segmented-control.pull-right
%li.segmented-control-item.segmented-control-item-active{ :onclick => "DS.toggleChart(event, '.monthly-dossiers-chart');" }
%li.segmented-control-item.segmented-control-item-active{ data: { 'toggle-chart': '.monthly-dossiers-chart' } }
Par mois
%li.segmented-control-item{ :onclick => "DS.toggleChart(event, '.cumulative-dossiers-chart');" }
%li.segmented-control-item{ data: { 'toggle-chart': '.cumulative-dossiers-chart' } }
Cumul
%span.stat-card-title.pull-left Dossiers déposés
.clearfix

View file

@ -29,8 +29,8 @@ module.exports = function(api) {
require('@babel/preset-env').default,
{
forceAllTransforms: true,
useBuiltIns: 'entry',
corejs: 2,
useBuiltIns: 'usage',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol']
}

View file

@ -39,6 +39,9 @@ Rails.application.configure do
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Suppress logger output for asset requests.
config.assets.quiet = true
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.

View file

@ -13,11 +13,11 @@
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"chartkick": "^3.2.0",
"core-js": "^2.0.0",
"core-js": "^3.6.4",
"debounce": "^1.2.0",
"dom4": "^2.1.5",
"email-butler": "^1.0.13",
"highcharts": "^6.1.2",
"highcharts": "^8.0.0",
"intersection-observer": "^0.7.0",
"jquery": "^3.4.1",
"leaflet": "^1.6.0",

View file

@ -162,6 +162,14 @@ FactoryBot.define do
end
end
trait :with_number do
after(:build) do |procedure, _evaluator|
type_de_champ = create(:type_de_champ_number)
procedure.types_de_champ << type_de_champ
end
end
trait :published do
after(:build) do |procedure, _evaluator|
procedure.path = generate(:published_path)

View file

@ -5,16 +5,15 @@ feature 'Signing up:' do
let(:user_password) { 'démarches-simplifiées-pwd' }
let(:procedure) { create :simple_procedure, :with_service }
scenario 'a new user can sign-up' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte demarches-simplifiees.fr'
scenario 'a new user can sign-up from scratch' do
visit new_user_registration_path
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_current_path commencer_path(path: procedure.path)
expect(page).to have_current_path dossiers_path
end
context 'when the user makes a typo in their email address' do
@ -68,11 +67,9 @@ feature 'Signing up:' do
context 'when visiting a procedure' do
let(:procedure) { create :simple_procedure, :with_service }
before do
visit commencer_path(path: procedure.path)
end
scenario 'a new user can sign-up and fill the procedure' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte'
expect(page).to have_current_path new_user_registration_path
expect(page).to have_procedure_description(procedure)
@ -80,8 +77,10 @@ feature 'Signing up:' do
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
click_confirmation_link_for(user_email, in_another_browser: true)
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content 'Votre compte a été activé'
click_on 'Commencer la démarche'
@ -103,12 +102,20 @@ feature 'Signing up:' do
sign_up_with user_email, user_password
# The same page than for initial sign-ups is displayed, to avoid leaking informations
# about the accound existence.
# about the account existence.
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
# The confirmation email is sent again
confirmation_email = open_email(user_email)
expect(confirmation_email.body).to have_text('Pour activer votre compte')
click_confirmation_link_for(user_email, in_another_browser: true)
# After confirmation, the user is redirected to the procedure they were initially starting
# (even when confirming the account in another browser).
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_content 'Commencer la démarche'
end
end
@ -131,8 +138,8 @@ feature 'Signing up:' do
warning_email = open_email(user_email)
expect(warning_email.body).to have_text('Votre compte existe déjà')
# When clicking the main button, the user has a link to directly sign-in
# for the procedure they were initially starting
# When clicking the main button, the user is redirected directly to
# the sign-in page for the procedure they were initially starting.
click_procedure_sign_in_link_for user_email
expect(page).to have_current_path new_user_session_path

View file

@ -3,6 +3,11 @@ class DeviseUserMailerPreview < ActionMailer::Preview
DeviseUserMailer.confirmation_instructions(user, "faketoken", {})
end
def confirmation_instructions___with_procedure
CurrentConfirmation.procedure_after_confirmation = procedure
DeviseUserMailer.confirmation_instructions(user, "faketoken", {})
end
def reset_password_instructions
DeviseUserMailer.reset_password_instructions(user, "faketoken", {})
end
@ -12,4 +17,8 @@ class DeviseUserMailerPreview < ActionMailer::Preview
def user
User.new(id: 10, email: "usager@example.com")
end
def procedure
Procedure.new(id: 20, libelle: 'Dotation dÉquipement des Territoires Ruraux - Exercice 2019', path: 'dotation-etr')
end
end

View file

@ -166,4 +166,25 @@ shared_examples 'type_de_champ_spec' do
expect(messages.last.starts_with?("La liste doit commencer par")).to be_truthy
end
end
describe '#type_de_champ_types_for' do
let(:procedure) { create(:procedure) }
let(:user) { create(:user) }
context 'when procedure without legacy "number"' do
it 'should have "nombre decimal" instead of "nombre"' do
expect(TypeDeChamp.type_de_champ_types_for(procedure, user).find { |tdc| tdc.last == TypeDeChamp.type_champs.fetch(:number) }).to be_nil
expect(TypeDeChamp.type_de_champ_types_for(procedure, user).find { |tdc| tdc.last == TypeDeChamp.type_champs.fetch(:decimal_number) }).not_to be_nil
end
end
context 'when procedure with legacy "number"' do
let(:procedure) { create(:procedure, :with_number) }
it 'should have "nombre decimal" and "nombre"' do
expect(TypeDeChamp.type_de_champ_types_for(procedure, user).find { |tdc| tdc.last == TypeDeChamp.type_champs.fetch(:number) }).not_to be_nil
expect(TypeDeChamp.type_de_champ_types_for(procedure, user).find { |tdc| tdc.last == TypeDeChamp.type_champs.fetch(:decimal_number) }).not_to be_nil
end
end
end
end

View file

@ -48,11 +48,16 @@ module FeatureHelpers
end
end
def click_confirmation_link_for(email)
def click_confirmation_link_for(email, in_another_browser: false)
confirmation_email = open_email(email)
token_params = confirmation_email.body.match(/confirmation_token=[^"]+/)
confirmation_link = confirmation_email.body.match(/href="[^"]*(\/users\/confirmation[^"]*)"/)[1]
visit "/users/confirmation?#{token_params}"
if in_another_browser
# Simulate the user opening the link in another browser, thus loosing the session cookie
Capybara.reset_session!
end
visit confirmation_link
end
def click_procedure_sign_in_link_for(email)

View file

@ -2572,16 +2572,16 @@ core-js-compat@^3.6.0:
browserslist "^4.8.3"
semver "7.0.0"
core-js@^2.0.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
core-js@^3.4.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.2.tgz#2799ea1a59050f0acf50dfe89b916d6503b16caa"
integrity sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==
core-js@^3.6.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -4426,10 +4426,10 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
highcharts@^6.1.2:
version "6.2.0"
resolved "https://registry.yarnpkg.com/highcharts/-/highcharts-6.2.0.tgz#2a6d04652eb43c66f462ca7e2d2808f1f2782b61"
integrity sha512-A4E89MA+kto8giic7zyLU6ZxfXnVeCUlKOyzFsah3+n4BROx4bgonl92KIBtwLud/mIWir8ahqhuhe2by9LakQ==
highcharts@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/highcharts/-/highcharts-8.0.0.tgz#fcf77a511d6ea477b9d8447afd9dedbfc85e2727"
integrity sha512-jRKLiP0i29zKSEgMDASyrfpivrM3e8V8yTv8YUZtRzB2mXR+hsBNHWZw2hNRZVxqq1QCVmW/9Z/BLFvYlJELqA==
highlight.js@~9.12.0:
version "9.12.0"