diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1ddb9651d97058bdc55a6d157ef77934eba9a2ad..de2794e4b4029b5a9457256af3a5d2439c7c699f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -185,6 +185,12 @@ executors:
 #                                                       COMMANDS                                                       #
 ########################################################################################################################
 
+# `default_parameters` isn't a key that CircleCI uses, but this form lets us reuse parameter definitions
+default_parameters: &Params
+  edition:
+    type: string
+    default: "oss"
+
 commands:
   attach-workspace:
     steps:
@@ -278,7 +284,7 @@ jobs:
       - run:
           name: Generate checksums of all backend source files to use as Uberjar cache key
           command: >
-            for file in `find ./src ./backend -type f -name '*.clj' | sort`;
+            for file in `find ./src ./backend ./enterprise/backend/src -type f -name '*.clj' | sort`;
               do echo `md5sum $file` >> backend-checksums.txt;
             done;
             echo `md5sum project.clj` >> backend-checksums.txt
@@ -286,7 +292,7 @@ jobs:
       - run:
           name: Generate checksums of all frontend source files to use as Uberjar cache key
           command: >
-            for file in `find ./frontend -type f | sort`;
+            for file in `find ./frontend ./enterpise/frontend -type f | sort`;
               do echo `md5sum $file` >> frontend-checksums.txt;
             done;
             echo `md5sum yarn.lock` >> frontend-checksums.txt
@@ -338,7 +344,9 @@ jobs:
           command: sudo apt-get install gettext
       - run:
           name: Check i18n tags/make sure template can be built
-          command: ./bin/i18n/update-translation-template
+          # fix OOM when running -- see https://stackoverflow.com/a/59572966/1198455
+          # without this option, sometimes this step will fail with an OOM error.
+          command: NODE_OPTIONS='--max-old-space-size=2048' ./bin/i18n/update-translation-template
           no_output_timeout: 2m
       - run:
           name: Verify i18n translations (.po files)
@@ -352,10 +360,12 @@ jobs:
 
   be-deps:
     executor: clojure
+    parameters:
+      <<: *Params
     steps:
       - attach-workspace
       - restore-be-deps-cache
-      - run: lein with-profile +include-all-drivers,+cloverage,+junit deps
+      - run: lein with-profile +include-all-drivers,+cloverage,+junit,+<< parameters.edition >> deps
       - save_cache:
           key: be-deps-v2-{{ checksum "project.clj" }}
           paths:
@@ -374,13 +384,14 @@ jobs:
       after-steps:
         type: steps
         default: []
+      <<: *Params
     executor: << parameters.e >>
     steps:
       - attach-workspace
       - restore-be-deps-cache
       - steps: << parameters.before-steps >>
       - run:
-          command: lein with-profile +ci << parameters.lein-command >>
+          command: lein with-profile +ci,+<< parameters.edition >> << parameters.lein-command >>
           no_output_timeout: 5m
       - steps: << parameters.after-steps >>
       - store_test_results:
@@ -427,9 +438,10 @@ jobs:
                 name: Test << parameters.driver >> driver << parameters.description >>
                 environment:
                   DRIVERS: h2,<< parameters.driver >>
+                  MB_EDITION: ee
                 command: >
                   /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh << parameters.driver >> ||
-                  lein with-profile +ci,+junit test
+                  lein with-profile +ci,+junit,+$MB_EDITION test
                 no_output_timeout: << parameters.timeout >>
       # This is exactly the same as without auto-retry but will try running the tests a second time if they fail
       - when:
@@ -439,9 +451,10 @@ jobs:
                 name: Test << parameters.driver >> driver
                 environment:
                   DRIVERS: h2,<< parameters.driver >>
+                  MB_EDITION: ee
                 command: >
                   /home/circleci/metabase/metabase/.circleci/skip-driver-tests.sh << parameters.driver >> ||
-                  lein with-profile +ci,+junit test || lein with-profile +ci,+junit test
+                  lein with-profile +ci,+junit,+$MB_EDITION test || lein with-profile +ci,+junit,+$MB_EDITION test
                 no_output_timeout: << parameters.timeout >>
       - store_test_results:
           path: /home/circleci/metabase/metabase/target/junit
@@ -452,6 +465,7 @@ jobs:
         type: executor
       db-type:
         type: string
+      <<: *Params
     executor: << parameters.e >>
     steps:
       - attach-workspace
@@ -461,6 +475,7 @@ jobs:
           environment:
             MB_DB_TYPE: << parameters.db-type >>
             MB_DB_HOST: localhost
+            MB_EDITION: << parameters.edition >>
           command: >
             ./bin/test-load-and-dump.sh
           no_output_timeout: 5m
@@ -544,6 +559,8 @@ jobs:
           command: run test-timezones
 
   build-uberjar:
+    parameters:
+      <<: *Params
     executor: clojure-and-node
     steps:
       - attach-workspace
@@ -551,7 +568,7 @@ jobs:
       # restore already-built uberjar
       - restore_cache:
           keys:
-            - uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
+            - uberjar-<< parameters.edition >>-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
       # restore the local maven installation of Metabase which is needed for building drivers
       - restore_cache:
           keys:
@@ -574,6 +591,8 @@ jobs:
             sudo ./linux-install-1.10.1.708.sh
       - run:
           name: Build frontend if needed
+          environment:
+            MB_EDITION: << parameters.edition >>
           command: >
             if [ ! -f './resources/frontend_client/index.html' ];
               then ./bin/build version frontend;
@@ -581,6 +600,8 @@ jobs:
           no_output_timeout: 5m
       - run:
           name: Build uberjar if needed
+          environment:
+            MB_EDITION: << parameters.edition >>
           command: >
             if [ ! -f './target/uberjar/metabase.jar' ]; then
               # INTERACTIVE=false will tell the clojure build scripts not to do interactive retries etc.
@@ -592,7 +613,7 @@ jobs:
           path: /home/circleci/metabase/metabase/target/uberjar/metabase.jar
       # Cache the built uberjar
       - save_cache:
-          key: uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
+          key: uberjar-<< parameters.edition >>-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
           paths:
             - /home/circleci/metabase/metabase/target/uberjar/metabase.jar
       # Cache the maven installation of metabase-core
@@ -639,6 +660,7 @@ jobs:
       driver:
         type: string
         default: ""
+      <<: *Params
     executor: << parameters.e >>
     environment:
       CYPRESS_GROUP:  << parameters.cypress-group >>
@@ -649,9 +671,11 @@ jobs:
           before-steps:
             - restore_cache:
                 keys:
-                  - uberjar-{{ checksum "./backend-checksums.txt" }}
+                  - uberjar-<< parameters.edition >>-{{ checksum "./backend-checksums.txt" }}
             - run:
                 name: Generate version file
+                environment:
+                  MB_EDITION: << parameters.edition >>
                 command: ./bin/build version
       - store_artifacts:
           path: /home/circleci/metabase/metabase/cypress
@@ -673,6 +697,12 @@ jobs:
 #                                                      WORKFLOWS                                                       #
 ########################################################################################################################
 
+# `default_matrix` isn't a key that CircleCI uses, but this form lets us reuse the matrix: block
+default_matrix: &Matrix
+  matrix:
+    parameters:
+      edition: ["ee", "oss"]
+
 workflows:
   version: 2
   build:
@@ -693,17 +723,19 @@ workflows:
             - checkout
 
       - lein:
-          name: be-tests
+          name: be-tests-<< matrix.edition >>
           requires:
             - be-deps
           lein-command: with-profile +junit test
+          <<: *Matrix
 
       - lein:
-          name: be-tests-java-11
+          name: be-tests-java-11-<< matrix.edition >>
           requires:
             - be-deps
           e: java-11
           lein-command: with-profile +junit test
+          <<: *Matrix
 
       - lein:
           name: be-linter-eastwood
@@ -744,70 +776,70 @@ workflows:
             - be-deps
 
       - test-driver:
-          name: be-tests-bigquery
+          name: be-tests-bigquery-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: bigquery
 
       - test-driver:
-          name: be-tests-druid
+          name: be-tests-druid-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: druid
 
       - test-driver:
-          name: be-tests-googleanalytics
+          name: be-tests-googleanalytics-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: googleanalytics
 
       - test-driver:
-          name: be-tests-mongo
+          name: be-tests-mongo-ee
           requires:
-            - be-tests
+            - be-tests-ee
           e: mongo
           driver: mongo
 
       - test-driver:
-          name: be-tests-mysql
+          name: be-tests-mysql-ee
           description: "(MySQL 5.7)"
           requires:
-            - be-tests
+            - be-tests-ee
           e:
             name: mysql-5-7
           driver: mysql
 
       - test-driver:
-          name: be-tests-mysql-latest
+          name: be-tests-mysql-latest-ee
           description: "(MySQL latest)"
           requires:
-            - be-tests
+            - be-tests-ee
           e:
             name: mysql-latest
           driver: mysql
 
       - test-driver:
-          name: be-tests-mariadb
+          name: be-tests-mariadb-ee
           description: "(MariaDB 10.2)"
           requires:
-            - be-tests
+            - be-tests-ee
           e:
             name: mariadb-10-2
           driver: mysql
 
       - test-driver:
-          name: be-tests-mariadb-latest
+          name: be-tests-mariadb-latest-ee
           description: "(MariaDB latest)"
           requires:
-            - be-tests
+            - be-tests-ee
           e:
             name: mariadb-latest
           driver: mysql
 
       - test-driver:
-          name: be-tests-oracle
+          name: be-tests-oracle-ee
           requires:
-            - be-tests
+            - be-tests-ee
           before-steps:
             - fetch-jdbc-driver:
                 source: ORACLE_JDBC_JAR
@@ -816,25 +848,25 @@ workflows:
           driver: oracle
 
       - test-driver:
-          name: be-tests-postgres
+          name: be-tests-postgres-ee
           description: "(9.6)"
           requires:
-            - be-tests
+            - be-tests-ee
           e: postgres-9-6
           driver: postgres
 
       - test-driver:
-          name: be-tests-postgres-latest
+          name: be-tests-postgres-latest-ee
           description: "(Latest)"
           requires:
-            - be-tests
+            - be-tests-ee
           e: postgres-latest
           driver: postgres
 
       - test-driver:
-          name: be-tests-presto
+          name: be-tests-presto-ee
           requires:
-            - be-tests
+            - be-tests-ee
           e: presto
           before-steps:
             - wait-for-port:
@@ -842,23 +874,23 @@ workflows:
           driver: presto
 
       - test-driver:
-          name: be-tests-redshift
+          name: be-tests-redshift-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: redshift
           timeout: 10m
 
       - test-driver:
-          name: be-tests-snowflake
+          name: be-tests-snowflake-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: snowflake
           timeout: 15m
 
       - test-driver:
-          name: be-tests-sparksql
+          name: be-tests-sparksql-ee
           requires:
-            - be-tests
+            - be-tests-ee
           e: sparksql
           before-steps:
             - wait-for-port:
@@ -866,22 +898,22 @@ workflows:
           driver: sparksql
 
       - test-driver:
-          name: be-tests-sqlite
+          name: be-tests-sqlite-ee
           requires:
-            - be-tests
+            - be-tests-ee
           driver: sqlite
 
       - test-driver:
-          name: be-tests-sqlserver
+          name: be-tests-sqlserver-ee
           requires:
-            - be-tests
+            - be-tests-ee
           e: sqlserver
           driver: sqlserver
 
       - test-driver:
-          name: be-tests-vertica
+          name: be-tests-vertica-ee
           requires:
-            - be-tests
+            - be-tests-ee
           e: vertica
           before-steps:
             - fetch-jdbc-driver:
@@ -892,18 +924,20 @@ workflows:
           auto-retry: true
 
       - test-migrate-from-h2:
-          name: be-tests-migrate-to-postgres
+          name: be-tests-migrate-to-postgres-<< matrix.edition >>
           requires:
-            - be-tests
+            - be-tests-<< matrix.edition >>
           e: postgres-9-6
           db-type: postgres
+          <<: *Matrix
 
       - test-migrate-from-h2:
-          name: be-tests-migrate-to-mysql
+          name: be-tests-migrate-to-mysql-<< matrix.edition >>
           requires:
-            - be-tests
+            - be-tests-<< matrix.edition >>
           e: mysql-5-7
           db-type: mysql
+          <<: *Matrix
 
       - fe-deps:
           requires:
@@ -930,99 +964,59 @@ workflows:
           requires:
             - fe-deps
       - build-uberjar:
+          name: build-uberjar-<< matrix.edition >>
           requires:
             - be-deps
+          <<: *Matrix
+
       - fe-tests-cypress:
-          name: fe-tests-cypress-1
+          name: fe-tests-cypress-1-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           cypress-group: "default"
+          <<: *Matrix
       - fe-tests-cypress:
-          name: fe-tests-cypress-2
+          name: fe-tests-cypress-2-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           cypress-group: "default"
+          <<: *Matrix
       - fe-tests-cypress:
-          name: fe-tests-cypress-3
+          name: fe-tests-cypress-3-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           cypress-group: "default"
+          <<: *Matrix
       - fe-tests-cypress:
-          name: fe-tests-cypress-4
+          name: fe-tests-cypress-4-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           cypress-group: "default"
+          <<: *Matrix
 
       - fe-tests-cypress:
-          name: fe-tests-cypress-mongo
+          name: fe-tests-cypress-mongo-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           e: fe-mongo
           cypress-group: "mongo"
           driver: mongo
           only-single-database: true
           test-files-location: frontend/test/metabase-db/mongo
+          <<: *Matrix
 
       - fe-tests-cypress:
-          name: fe-tests-cypress-postgres-12
+          name: fe-tests-cypress-postgres-12-<< matrix.edition >>
           requires:
-            - build-uberjar
+            - build-uberjar-<< matrix.edition >>
             - fe-deps
           e: fe-postgres-12
           cypress-group: "postgres"
           only-single-database: true
           test-files-location: frontend/test/metabase-db/postgres
-
-      - deploy-master:
-          requires:
-            - yaml-linter
-            - verify-i18n-files
-
-            - be-linter-bikeshed
-            - be-linter-cloverage
-            - be-linter-docstring-checker
-            - be-linter-eastwood
-            - be-linter-namespace-decls
-            - be-linter-reflection-warnings
-
-            - be-tests
-            - be-tests-java-11
-
-            - be-tests-bigquery
-            - be-tests-druid
-            - be-tests-googleanalytics
-            - be-tests-mongo
-            - be-tests-mysql
-            - be-tests-mysql-latest
-            - be-tests-mariadb
-            - be-tests-mariadb-latest
-            - be-tests-oracle
-            - be-tests-postgres
-            - be-tests-postgres-latest
-            - be-tests-presto
-            - be-tests-redshift
-            - be-tests-snowflake
-            - be-tests-sparksql
-            - be-tests-sqlite
-            - be-tests-sqlserver
-            - be-tests-vertica
-
-            - be-tests-migrate-to-mysql
-            - be-tests-migrate-to-postgres
-
-            - fe-linter-eslint
-            - fe-linter-flow
-            - fe-linter-prettier
-            - fe-linter-docs-links
-
-            - fe-tests-integration
-            - fe-tests-timezones
-            - fe-tests-unit
-          filters:
-            branches:
-              only: master
+          <<: *Matrix
diff --git a/.circleci/skip-driver-tests.sh b/.circleci/skip-driver-tests.sh
index 10faa0f27430140701c1d58fd61c0fc941317a16..a0c0950c2d662d11b9e4015c0231fe1d72399977 100755
--- a/.circleci/skip-driver-tests.sh
+++ b/.circleci/skip-driver-tests.sh
@@ -11,7 +11,7 @@ set -eu
 
 COMMIT_MESSAGE=`cat commit.txt`
 
-! [[ "$CIRCLE_BRANCH" =~ ^master|release-.+$ ]] &&
+! [[ "$CIRCLE_BRANCH" =~ ^master|release-.+$|enterprise ]] &&
     ! [[ "$COMMIT_MESSAGE" == *"[ci all]"* ]] &&
     ! [[ "$COMMIT_MESSAGE" == *"[ci drivers]"* ]] &&
     ! [[ "$COMMIT_MESSAGE" == *"[ci $1]"* ]] &&
diff --git a/.eslintrc b/.eslintrc
index bff42e6f484a34937ddd7a60479a8dfb6d9640e6..9241a32071ddaaf2e821d6bc612533cb9a14e5e5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -18,12 +18,14 @@
     "react/no-find-dom-node": 0,
     "flowtype/define-flow-type": 1,
     "flowtype/use-flow-type": 1,
-    "prefer-const": [1, { "destructuring": "all" }]
+    "prefer-const": [1, { "destructuring": "all" }],
+    "no-useless-escape": 0
   },
   "globals": {
     "pending": false,
+    "before": true,
     "cy": true,
-    "Cypress": true
+    "Cypress": true,
   },
   "env": {
     "browser": true,
@@ -42,5 +44,10 @@
   "settings": {
     "import/resolver": "webpack",
     "import/ignore": ["\\.css$"]
+  },
+  "parserOptions": {
+    "ecmaFeatures": {
+      "legacyDecorators": true
+    }
   }
 }
diff --git a/.flowconfig b/.flowconfig
index d7aeb0d0a5ecfd0aad2ec1362662c99bdbee4067..9b01ad0d3d4e28ab90068d6a75260605cef4e60a 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -17,9 +17,14 @@
 .*/node_modules/@testing-library/jest-dom/.*
 .*/node_modules/.*/node_modules/chalk/.*
 .*/node_modules/react-draggable/lib/.*
+.*/node_modules/redux-form/es/.*
+.*/node_modules/annotate-react-dom/src/annotate-react-dom.js
+.*/node_modules/csstype/.*
+.*/node_modules/gensync/.*
 
 [include]
 .*/frontend/.*
+.*/enterprise/frontend/.*
 
 [libs]
 ./frontend/interfaces
@@ -31,6 +36,7 @@ esproposal.decorators=ignore
 esproposal.class_static_fields=enable
 esproposal.class_instance_fields=enable
 suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
+module.name_mapper='^metabase-enterprise\/\(.*\)$' -> '<PROJECT_ROOT>/enterprise/frontend/src/metabase-enterprise/\1'
 module.name_mapper='.*\(\.css\)' -> 'CSSModule'
 module.name_mapper='^ace/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/ace-builds/src-min-noconflict/\1'
 module.name_mapper='^assets\/\(.*\)$' -> '<PROJECT_ROOT>/resources/frontend_client/app/assets/\1'
diff --git a/LICENSE-AGPL.txt b/LICENSE-AGPL.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3dfe01c925f42ba5b08f34fc8084d983108484a3
--- /dev/null
+++ b/LICENSE-AGPL.txt
@@ -0,0 +1,711 @@
+    GNU AFFERO GENERAL PUBLIC LICENSE
+    Version 3, 19 November 2007
+
+    Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+    Everyone is permitted to copy and distribute verbatim copies
+    of this license document, but changing it is not allowed.
+
+    Preamble
+
+    The GNU Affero General Public License is a free, copyleft license for
+    software and other kinds of works, specifically designed to ensure
+    cooperation with the community in the case of network server software.
+
+    The licenses for most software and other practical works are designed
+    to take away your freedom to share and change the works.  By contrast,
+    our General Public Licenses are intended to guarantee your freedom to
+    share and change all versions of a program--to make sure it remains free
+    software for all its users.
+
+    When we speak of free software, we are referring to freedom, not
+    price.  Our General Public Licenses are designed to make sure that you
+    have the freedom to distribute copies of free software (and charge for
+    them if you wish), that you receive source code or can get it if you
+    want it, that you can change the software or use pieces of it in new
+    free programs, and that you know you can do these things.
+
+    Developers that use our General Public Licenses protect your rights
+    with two steps: (1) assert copyright on the software, and (2) offer
+    you this License which gives you legal permission to copy, distribute
+    and/or modify the software.
+
+    A secondary benefit of defending all users' freedom is that
+    improvements made in alternate versions of the program, if they
+    receive widespread use, become available for other developers to
+    incorporate.  Many developers of free software are heartened and
+    encouraged by the resulting cooperation.  However, in the case of
+    software used on network servers, this result may fail to come about.
+    The GNU General Public License permits making a modified version and
+    letting the public access it on a server without ever releasing its
+    source code to the public.
+
+    The GNU Affero General Public License is designed specifically to
+    ensure that, in such cases, the modified source code becomes available
+    to the community.  It requires the operator of a network server to
+    provide the source code of the modified version running there to the
+    users of that server.  Therefore, public use of a modified version, on
+    a publicly accessible server, gives the public access to the source
+    code of the modified version.
+
+    An older license, called the Affero General Public License and
+    published by Affero, was designed to accomplish similar goals.  This is
+    a different license, not a version of the Affero GPL, but Affero has
+    released a new version of the Affero GPL which permits relicensing under
+    this license.
+
+    The precise terms and conditions for copying, distribution and
+    modification follow.
+
+    TERMS AND CONDITIONS
+
+    0. Definitions.
+
+    "This License" refers to version 3 of the GNU Affero General Public License.
+
+    "Copyright" also means copyright-like laws that apply to other kinds of
+    works, such as semiconductor masks.
+
+    "The Program" refers to any copyrightable work licensed under this
+    License.  Each licensee is addressed as "you".  "Licensees" and
+    "recipients" may be individuals or organizations.
+
+    To "modify" a work means to copy from or adapt all or part of the work
+    in a fashion requiring copyright permission, other than the making of an
+    exact copy.  The resulting work is called a "modified version" of the
+    earlier work or a work "based on" the earlier work.
+
+    A "covered work" means either the unmodified Program or a work based
+    on the Program.
+
+    To "propagate" a work means to do anything with it that, without
+    permission, would make you directly or secondarily liable for
+    infringement under applicable copyright law, except executing it on a
+    computer or modifying a private copy.  Propagation includes copying,
+    distribution (with or without modification), making available to the
+    public, and in some countries other activities as well.
+
+    To "convey" a work means any kind of propagation that enables other
+    parties to make or receive copies.  Mere interaction with a user through
+    a computer network, with no transfer of a copy, is not conveying.
+
+    An interactive user interface displays "Appropriate Legal Notices"
+    to the extent that it includes a convenient and prominently visible
+    feature that (1) displays an appropriate copyright notice, and (2)
+    tells the user that there is no warranty for the work (except to the
+    extent that warranties are provided), that licensees may convey the
+    work under this License, and how to view a copy of this License.  If
+    the interface presents a list of user commands or options, such as a
+    menu, a prominent item in the list meets this criterion.
+
+    1. Source Code.
+
+    The "source code" for a work means the preferred form of the work
+    for making modifications to it.  "Object code" means any non-source
+    form of a work.
+
+    A "Standard Interface" means an interface that either is an official
+    standard defined by a recognized standards body, or, in the case of
+    interfaces specified for a particular programming language, one that
+    is widely used among developers working in that language.
+
+    The "System Libraries" of an executable work include anything, other
+    than the work as a whole, that (a) is included in the normal form of
+    packaging a Major Component, but which is not part of that Major
+    Component, and (b) serves only to enable use of the work with that
+    Major Component, or to implement a Standard Interface for which an
+    implementation is available to the public in source code form.  A
+    "Major Component", in this context, means a major essential component
+    (kernel, window system, and so on) of the specific operating system
+    (if any) on which the executable work runs, or a compiler used to
+    produce the work, or an object code interpreter used to run it.
+
+    The "Corresponding Source" for a work in object code form means all
+    the source code needed to generate, install, and (for an executable
+    work) run the object code and to modify the work, including scripts to
+    control those activities.  However, it does not include the work's
+    System Libraries, or general-purpose tools or generally available free
+    programs which are used unmodified in performing those activities but
+    which are not part of the work.  For example, Corresponding Source
+    includes interface definition files associated with source files for
+    the work, and the source code for shared libraries and dynamically
+    linked subprograms that the work is specifically designed to require,
+    such as by intimate data communication or control flow between those
+    subprograms and other parts of the work.
+
+    The Corresponding Source need not include anything that users
+    can regenerate automatically from other parts of the Corresponding
+    Source.
+
+    The Corresponding Source for a work in source code form is that
+    same work.
+
+    2. Basic Permissions.
+
+    All rights granted under this License are granted for the term of
+    copyright on the Program, and are irrevocable provided the stated
+    conditions are met.  This License explicitly affirms your unlimited
+    permission to run the unmodified Program.  The output from running a
+    covered work is covered by this License only if the output, given its
+    content, constitutes a covered work.  This License acknowledges your
+    rights of fair use or other equivalent, as provided by copyright law.
+
+    You may make, run and propagate covered works that you do not
+    convey, without conditions so long as your license otherwise remains
+    in force.  You may convey covered works to others for the sole purpose
+    of having them make modifications exclusively for you, or provide you
+    with facilities for running those works, provided that you comply with
+    the terms of this License in conveying all material for which you do
+    not control copyright.  Those thus making or running the covered works
+    for you must do so exclusively on your behalf, under your direction
+    and control, on terms that prohibit them from making any copies of
+    your copyrighted material outside their relationship with you.
+
+    Conveying under any other circumstances is permitted solely under
+    the conditions stated below.  Sublicensing is not allowed; section 10
+    makes it unnecessary.
+
+    3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+    No covered work shall be deemed part of an effective technological
+    measure under any applicable law fulfilling obligations under article
+    11 of the WIPO copyright treaty adopted on 20 December 1996, or
+    similar laws prohibiting or restricting circumvention of such
+    measures.
+
+    When you convey a covered work, you waive any legal power to forbid
+    circumvention of technological measures to the extent such circumvention
+    is effected by exercising rights under this License with respect to
+    the covered work, and you disclaim any intention to limit operation or
+    modification of the work as a means of enforcing, against the work's
+    users, your or third parties' legal rights to forbid circumvention of
+    technological measures.
+
+    4. Conveying Verbatim Copies.
+
+    You may convey verbatim copies of the Program's source code as you
+    receive it, in any medium, provided that you conspicuously and
+    appropriately publish on each copy an appropriate copyright notice;
+    keep intact all notices stating that this License and any
+    non-permissive terms added in accord with section 7 apply to the code;
+    keep intact all notices of the absence of any warranty; and give all
+    recipients a copy of this License along with the Program.
+
+    You may charge any price or no price for each copy that you convey,
+    and you may offer support or warranty protection for a fee.
+
+    5. Conveying Modified Source Versions.
+
+    You may convey a work based on the Program, or the modifications to
+    produce it from the Program, in the form of source code under the
+    terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+    A compilation of a covered work with other separate and independent
+    works, which are not by their nature extensions of the covered work,
+    and which are not combined with it such as to form a larger program,
+    in or on a volume of a storage or distribution medium, is called an
+    "aggregate" if the compilation and its resulting copyright are not
+    used to limit the access or legal rights of the compilation's users
+    beyond what the individual works permit.  Inclusion of a covered work
+    in an aggregate does not cause this License to apply to the other
+    parts of the aggregate.
+
+    6. Conveying Non-Source Forms.
+
+    You may convey a covered work in object code form under the terms
+    of sections 4 and 5, provided that you also convey the
+    machine-readable Corresponding Source under the terms of this License,
+    in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+    A separable portion of the object code, whose source code is excluded
+    from the Corresponding Source as a System Library, need not be
+    included in conveying the object code work.
+
+    A "User Product" is either (1) a "consumer product", which means any
+    tangible personal property which is normally used for personal, family,
+    or household purposes, or (2) anything designed or sold for incorporation
+    into a dwelling.  In determining whether a product is a consumer product,
+    doubtful cases shall be resolved in favor of coverage.  For a particular
+    product received by a particular user, "normally used" refers to a
+    typical or common use of that class of product, regardless of the status
+    of the particular user or of the way in which the particular user
+    actually uses, or expects or is expected to use, the product.  A product
+    is a consumer product regardless of whether the product has substantial
+    commercial, industrial or non-consumer uses, unless such uses represent
+    the only significant mode of use of the product.
+
+    "Installation Information" for a User Product means any methods,
+    procedures, authorization keys, or other information required to install
+    and execute modified versions of a covered work in that User Product from
+    a modified version of its Corresponding Source.  The information must
+    suffice to ensure that the continued functioning of the modified object
+    code is in no case prevented or interfered with solely because
+    modification has been made.
+
+    If you convey an object code work under this section in, or with, or
+    specifically for use in, a User Product, and the conveying occurs as
+    part of a transaction in which the right of possession and use of the
+    User Product is transferred to the recipient in perpetuity or for a
+    fixed term (regardless of how the transaction is characterized), the
+    Corresponding Source conveyed under this section must be accompanied
+    by the Installation Information.  But this requirement does not apply
+    if neither you nor any third party retains the ability to install
+    modified object code on the User Product (for example, the work has
+    been installed in ROM).
+
+    The requirement to provide Installation Information does not include a
+    requirement to continue to provide support service, warranty, or updates
+    for a work that has been modified or installed by the recipient, or for
+    the User Product in which it has been modified or installed.  Access to a
+    network may be denied when the modification itself materially and
+    adversely affects the operation of the network or violates the rules and
+    protocols for communication across the network.
+
+    Corresponding Source conveyed, and Installation Information provided,
+    in accord with this section must be in a format that is publicly
+    documented (and with an implementation available to the public in
+    source code form), and must require no special password or key for
+    unpacking, reading or copying.
+
+    7. Additional Terms.
+
+    "Additional permissions" are terms that supplement the terms of this
+    License by making exceptions from one or more of its conditions.
+    Additional permissions that are applicable to the entire Program shall
+    be treated as though they were included in this License, to the extent
+    that they are valid under applicable law.  If additional permissions
+    apply only to part of the Program, that part may be used separately
+    under those permissions, but the entire Program remains governed by
+    this License without regard to the additional permissions.
+
+    When you convey a copy of a covered work, you may at your option
+    remove any additional permissions from that copy, or from any part of
+    it.  (Additional permissions may be written to require their own
+    removal in certain cases when you modify the work.)  You may place
+    additional permissions on material, added by you to a covered work,
+    for which you have or can give appropriate copyright permission.
+
+    Notwithstanding any other provision of this License, for material you
+    add to a covered work, you may (if authorized by the copyright holders of
+    that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+    All other non-permissive additional terms are considered "further
+    restrictions" within the meaning of section 10.  If the Program as you
+    received it, or any part of it, contains a notice stating that it is
+    governed by this License along with a term that is a further
+    restriction, you may remove that term.  If a license document contains
+    a further restriction but permits relicensing or conveying under this
+    License, you may add to a covered work material governed by the terms
+    of that license document, provided that the further restriction does
+    not survive such relicensing or conveying.
+
+    If you add terms to a covered work in accord with this section, you
+    must place, in the relevant source files, a statement of the
+    additional terms that apply to those files, or a notice indicating
+    where to find the applicable terms.
+
+    Additional terms, permissive or non-permissive, may be stated in the
+    form of a separately written license, or stated as exceptions;
+    the above requirements apply either way.
+
+    8. Termination.
+
+    You may not propagate or modify a covered work except as expressly
+    provided under this License.  Any attempt otherwise to propagate or
+    modify it is void, and will automatically terminate your rights under
+    this License (including any patent licenses granted under the third
+    paragraph of section 11).
+
+    However, if you cease all violation of this License, then your
+    license from a particular copyright holder is reinstated (a)
+    provisionally, unless and until the copyright holder explicitly and
+    finally terminates your license, and (b) permanently, if the copyright
+    holder fails to notify you of the violation by some reasonable means
+    prior to 60 days after the cessation.
+
+    Moreover, your license from a particular copyright holder is
+    reinstated permanently if the copyright holder notifies you of the
+    violation by some reasonable means, this is the first time you have
+    received notice of violation of this License (for any work) from that
+    copyright holder, and you cure the violation prior to 30 days after
+    your receipt of the notice.
+
+    Termination of your rights under this section does not terminate the
+    licenses of parties who have received copies or rights from you under
+    this License.  If your rights have been terminated and not permanently
+    reinstated, you do not qualify to receive new licenses for the same
+    material under section 10.
+
+    9. Acceptance Not Required for Having Copies.
+
+    You are not required to accept this License in order to receive or
+    run a copy of the Program.  Ancillary propagation of a covered work
+    occurring solely as a consequence of using peer-to-peer transmission
+    to receive a copy likewise does not require acceptance.  However,
+    nothing other than this License grants you permission to propagate or
+    modify any covered work.  These actions infringe copyright if you do
+    not accept this License.  Therefore, by modifying or propagating a
+    covered work, you indicate your acceptance of this License to do so.
+
+    10. Automatic Licensing of Downstream Recipients.
+
+    Each time you convey a covered work, the recipient automatically
+    receives a license from the original licensors, to run, modify and
+    propagate that work, subject to this License.  You are not responsible
+    for enforcing compliance by third parties with this License.
+
+    An "entity transaction" is a transaction transferring control of an
+    organization, or substantially all assets of one, or subdividing an
+    organization, or merging organizations.  If propagation of a covered
+    work results from an entity transaction, each party to that
+    transaction who receives a copy of the work also receives whatever
+    licenses to the work the party's predecessor in interest had or could
+    give under the previous paragraph, plus a right to possession of the
+    Corresponding Source of the work from the predecessor in interest, if
+    the predecessor has it or can get it with reasonable efforts.
+
+    You may not impose any further restrictions on the exercise of the
+    rights granted or affirmed under this License.  For example, you may
+    not impose a license fee, royalty, or other charge for exercise of
+    rights granted under this License, and you may not initiate litigation
+    (including a cross-claim or counterclaim in a lawsuit) alleging that
+    any patent claim is infringed by making, using, selling, offering for
+    sale, or importing the Program or any portion of it.
+
+    11. Patents.
+
+    A "contributor" is a copyright holder who authorizes use under this
+    License of the Program or a work on which the Program is based.  The
+    work thus licensed is called the contributor's "contributor version".
+
+    A contributor's "essential patent claims" are all patent claims
+    owned or controlled by the contributor, whether already acquired or
+    hereafter acquired, that would be infringed by some manner, permitted
+    by this License, of making, using, or selling its contributor version,
+    but do not include claims that would be infringed only as a
+    consequence of further modification of the contributor version.  For
+    purposes of this definition, "control" includes the right to grant
+    patent sublicenses in a manner consistent with the requirements of
+    this License.
+
+    Each contributor grants you a non-exclusive, worldwide, royalty-free
+    patent license under the contributor's essential patent claims, to
+    make, use, sell, offer for sale, import and otherwise run, modify and
+    propagate the contents of its contributor version.
+
+    In the following three paragraphs, a "patent license" is any express
+    agreement or commitment, however denominated, not to enforce a patent
+    (such as an express permission to practice a patent or covenant not to
+    sue for patent infringement).  To "grant" such a patent license to a
+    party means to make such an agreement or commitment not to enforce a
+    patent against the party.
+
+    If you convey a covered work, knowingly relying on a patent license,
+    and the Corresponding Source of the work is not available for anyone
+    to copy, free of charge and under the terms of this License, through a
+    publicly available network server or other readily accessible means,
+    then you must either (1) cause the Corresponding Source to be so
+    available, or (2) arrange to deprive yourself of the benefit of the
+    patent license for this particular work, or (3) arrange, in a manner
+    consistent with the requirements of this License, to extend the patent
+    license to downstream recipients.  "Knowingly relying" means you have
+    actual knowledge that, but for the patent license, your conveying the
+    covered work in a country, or your recipient's use of the covered work
+    in a country, would infringe one or more identifiable patents in that
+    country that you have reason to believe are valid.
+
+    If, pursuant to or in connection with a single transaction or
+    arrangement, you convey, or propagate by procuring conveyance of, a
+    covered work, and grant a patent license to some of the parties
+    receiving the covered work authorizing them to use, propagate, modify
+    or convey a specific copy of the covered work, then the patent license
+    you grant is automatically extended to all recipients of the covered
+    work and works based on it.
+
+    A patent license is "discriminatory" if it does not include within
+    the scope of its coverage, prohibits the exercise of, or is
+    conditioned on the non-exercise of one or more of the rights that are
+    specifically granted under this License.  You may not convey a covered
+    work if you are a party to an arrangement with a third party that is
+    in the business of distributing software, under which you make payment
+    to the third party based on the extent of your activity of conveying
+    the work, and under which the third party grants, to any of the
+    parties who would receive the covered work from you, a discriminatory
+    patent license (a) in connection with copies of the covered work
+    conveyed by you (or copies made from those copies), or (b) primarily
+    for and in connection with specific products or compilations that
+    contain the covered work, unless you entered into that arrangement,
+    or that patent license was granted, prior to 28 March 2007.
+
+    Nothing in this License shall be construed as excluding or limiting
+    any implied license or other defenses to infringement that may
+    otherwise be available to you under applicable patent law.
+
+    12. No Surrender of Others' Freedom.
+
+    If conditions are imposed on you (whether by court order, agreement or
+    otherwise) that contradict the conditions of this License, they do not
+    excuse you from the conditions of this License.  If you cannot convey a
+    covered work so as to satisfy simultaneously your obligations under this
+    License and any other pertinent obligations, then as a consequence you may
+    not convey it at all.  For example, if you agree to terms that obligate you
+    to collect a royalty for further conveying from those to whom you convey
+    the Program, the only way you could satisfy both those terms and this
+    License would be to refrain entirely from conveying the Program.
+
+    13. Remote Network Interaction; Use with the GNU General Public License.
+
+    Notwithstanding any other provision of this License, if you modify the
+    Program, your modified version must prominently offer all users
+    interacting with it remotely through a computer network (if your version
+    supports such interaction) an opportunity to receive the Corresponding
+    Source of your version by providing access to the Corresponding Source
+    from a network server at no charge, through some standard or customary
+    means of facilitating copying of software.  This Corresponding Source
+    shall include the Corresponding Source for any work covered by version 3
+    of the GNU General Public License that is incorporated pursuant to the
+    following paragraph.
+
+    Notwithstanding any other provision of this License, you have
+    permission to link or combine any covered work with a work licensed
+    under version 3 of the GNU General Public License into a single
+    combined work, and to convey the resulting work.  The terms of this
+    License will continue to apply to the part which is the covered work,
+    but the work with which it is combined will remain governed by version
+    3 of the GNU General Public License.
+
+    14. Revised Versions of this License.
+
+    The Free Software Foundation may publish revised and/or new versions of
+    the GNU Affero General Public License from time to time.  Such new versions
+    will be similar in spirit to the present version, but may differ in detail to
+    address new problems or concerns.
+
+    Each version is given a distinguishing version number.  If the
+    Program specifies that a certain numbered version of the GNU Affero General
+    Public License "or any later version" applies to it, you have the
+    option of following the terms and conditions either of that numbered
+    version or of any later version published by the Free Software
+    Foundation.  If the Program does not specify a version number of the
+    GNU Affero General Public License, you may choose any version ever published
+    by the Free Software Foundation.
+
+    If the Program specifies that a proxy can decide which future
+    versions of the GNU Affero General Public License can be used, that proxy's
+    public statement of acceptance of a version permanently authorizes you
+    to choose that version for the Program.
+
+    Later license versions may give you additional or different
+    permissions.  However, no additional obligations are imposed on any
+    author or copyright holder as a result of your choosing to follow a
+    later version.
+
+    15. Disclaimer of Warranty.
+
+    THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+    APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+    HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+    OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+    IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+    ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+    16. Limitation of Liability.
+
+    IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+    WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+    THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+    GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+    USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+    DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+    PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+    EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGES.
+
+    17. Interpretation of Sections 15 and 16.
+
+    If the disclaimer of warranty and limitation of liability provided
+    above cannot be given local legal effect according to their terms,
+    reviewing courts shall apply local law that most closely approximates
+    an absolute waiver of all civil liability in connection with the
+    Program, unless a warranty or assumption of liability accompanies a
+    copy of the Program in return for a fee.
+
+    END OF TERMS AND CONDITIONS
+
+    How to Apply These Terms to Your New Programs
+
+    If you develop a new program, and you want it to be of the greatest
+    possible use to the public, the best way to achieve this is to make it
+    free software which everyone can redistribute and change under these terms.
+
+    To do so, attach the following notices to the program.  It is safest
+    to attach them to the start of each source file to most effectively
+    state the exclusion of warranty; and each file should have at least
+    the "copyright" line and a pointer to where the full notice is found.
+
+        <one line to give the program's name and a brief idea of what it does.>
+        Copyright (C) <year>  <name of author>
+
+        This program is free software: you can redistribute it and/or modify
+        it under the terms of the GNU Affero General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        This program is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+        GNU Affero General Public License for more details.
+
+        You should have received a copy of the GNU Affero General Public License
+        along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+    Also add information on how to contact you by electronic and paper mail.
+
+    If your software can interact with users remotely through a computer
+    network, you should also make sure that it provides a way for users to
+    get its source.  For example, if your program is a web application, its
+    interface could display a "Source" link that leads users to an archive
+    of the code.  There are many ways you could offer source, and different
+    solutions will be better for different programs; see section 13 for the
+    specific requirements.
+
+    You should also get your employer (if you work as a programmer) or school,
+    if any, to sign a "copyright disclaimer" for the program, if necessary.
+    For more information on this, and how to apply and follow the GNU AGPL, see
+    <http://www.gnu.org/licenses/>.
+
+
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or combining it with any of the below libraries (or a modified version of that library), containing parts covered by the terms of any of the below libraries, the licensors of this Program grant you additional permission to convey the resulting work.
+
+* cider/cider-nrepl
+* clojure-complete
+* clojurewerkz/quartzite
+* clojurewerkz/support
+* clout
+* colorize
+* com.cemerick/friend
+* compojure
+* environ
+* hiccup
+* junit
+* korma
+* medley
+* org.clojure/clojure
+* org.clojure/clojurescript
+* org.clojure/core.async
+* org.clojure/core.cache
+* org.clojure/core.incubator
+* org.clojure/core.logic
+* org.clojure/core.match
+* org.clojure/core.memoize
+* org.clojure/data.csv
+* org.clojure/data.json
+* org.clojure/data.priority-map
+* org.clojure/java.classpath
+* org.clojure/java.jdbc
+* org.clojure/math.numeric-tower
+* org.clojure/tools.analyzer
+* org.clojure/tools.analyzer.jvm
+* org.clojure/tools.logging
+* org.clojure/tools.macro
+* org.clojure/tools.namespace
+* org.clojure/tools.nrepl
+* org.clojure/tools.reader
+* org.tcrawley/dynapath
+* quoin
+* refactor-nrepl
+* robert/hooke
+* scout
+* slingshot
+* stencil
+* swiss-arrows
+* tigris
+* instaparse
diff --git a/LICENSE-MCL.txt b/LICENSE-MCL.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b866cac7e7c9bdfd8970acbd52e4f980dd6406ae
--- /dev/null
+++ b/LICENSE-MCL.txt
@@ -0,0 +1 @@
+Usage of files in the top-level "/enterprise" directory and subdirectories thereof, and of Metabase Enterprise Edition features, is subject to the Metabase Commercial License (https://www.metabase.com/license/commercial/), and conditional on having a valid license from Metabase. Access to files in this directory and its subdirectories does not constitute permission to use this code or Metabase Enterprise Edition features.
diff --git a/LICENSE.txt b/LICENSE.txt
index b04eb24b311ab0b60290742b76256132186a0d4a..daf7749c94a53c3ec3d2da8de673f298cb36e2c5 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,710 +1,7 @@
-                    GNU AFFERO GENERAL PUBLIC LICENSE
-                       Version 3, 19 November 2007
+Source code in this repository is variously licensed under the GNU Affero General Public License (AGPL), or the Metabase Commercial License (https://www.metabase.com/license/commercial/).
 
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+* Outside of the top-level "enterprise" directory, source code in a given file is licensed under the AGPL.
 
-                            Preamble
+* Within the the top-level "enterprise" directory, source code in a given file is licensed under the Metabase Commercial License, unless otherwise noted.
 
-  The GNU Affero General Public License is a free, copyleft license for
-software and other kinds of works, specifically designed to ensure
-cooperation with the community in the case of network server software.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-our General Public Licenses are intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  Developers that use our General Public Licenses protect your rights
-with two steps: (1) assert copyright on the software, and (2) offer
-you this License which gives you legal permission to copy, distribute
-and/or modify the software.
-
-  A secondary benefit of defending all users' freedom is that
-improvements made in alternate versions of the program, if they
-receive widespread use, become available for other developers to
-incorporate.  Many developers of free software are heartened and
-encouraged by the resulting cooperation.  However, in the case of
-software used on network servers, this result may fail to come about.
-The GNU General Public License permits making a modified version and
-letting the public access it on a server without ever releasing its
-source code to the public.
-
-  The GNU Affero General Public License is designed specifically to
-ensure that, in such cases, the modified source code becomes available
-to the community.  It requires the operator of a network server to
-provide the source code of the modified version running there to the
-users of that server.  Therefore, public use of a modified version, on
-a publicly accessible server, gives the public access to the source
-code of the modified version.
-
-  An older license, called the Affero General Public License and
-published by Affero, was designed to accomplish similar goals.  This is
-a different license, not a version of the Affero GPL, but Affero has
-released a new version of the Affero GPL which permits relicensing under
-this license.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU Affero General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Remote Network Interaction; Use with the GNU General Public License.
-
-  Notwithstanding any other provision of this License, if you modify the
-Program, your modified version must prominently offer all users
-interacting with it remotely through a computer network (if your version
-supports such interaction) an opportunity to receive the Corresponding
-Source of your version by providing access to the Corresponding Source
-from a network server at no charge, through some standard or customary
-means of facilitating copying of software.  This Corresponding Source
-shall include the Corresponding Source for any work covered by version 3
-of the GNU General Public License that is incorporated pursuant to the
-following paragraph.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the work with which it is combined will remain governed by version
-3 of the GNU General Public License.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU Affero General Public License from time to time.  Such new versions
-will be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU Affero General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU Affero General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU Affero General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU Affero General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU Affero General Public License for more details.
-
-    You should have received a copy of the GNU Affero General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If your software can interact with users remotely through a computer
-network, you should also make sure that it provides a way for users to
-get its source.  For example, if your program is a web application, its
-interface could display a "Source" link that leads users to an archive
-of the code.  There are many ways you could offer source, and different
-solutions will be better for different programs; see section 13 for the
-specific requirements.
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU AGPL, see
-<http://www.gnu.org/licenses/>.
-
-Additional permission under GNU GPL version 3 section 7
-
-If you modify this Program, or any covered work, by linking or combining it with any of the below libraries (or a modified version of that library), containing parts covered by the terms of any of the below libraries, the licensors of this Program grant you additional permission to convey the resulting work. 
-
-cider/cider-nrepl
-clojure-complete
-clojurewerkz/quartzite
-clojurewerkz/support
-clout
-colorize
-com.cemerick/friend 
-compojure 
-environ 
-hiccup 
-junit
-korma
-medley
-org.clojure/clojure
-org.clojure/clojurescript
-org.clojure/core.async
-org.clojure/core.cache
-org.clojure/core.incubator
-org.clojure/core.logic
-org.clojure/core.match
-org.clojure/core.memoize
-org.clojure/data.csv
-org.clojure/data.json
-org.clojure/data.priority-map
-org.clojure/java.classpath
-org.clojure/java.jdbc
-org.clojure/math.numeric-tower
-org.clojure/tools.analyzer
-org.clojure/tools.analyzer.jvm
-org.clojure/tools.logging 
-org.clojure/tools.macro 
-org.clojure/tools.namespace 
-org.clojure/tools.nrepl
-org.clojure/tools.reader 
-org.tcrawley/dynapath 
-quoin 
-refactor-nrepl 
-robert/hooke 
-scout 
-slingshot 
-stencil 
-swiss-arrows 
-tigris 
-instaparse
+When built, binary files are generated for the AGPL source code and the Metabase Commercial License source code. Binaries located at hub.docker.com/metabase/metabase-enterprise and downloads.metabase.com/enterprise are released under the Metabase Commercial License. Binaries located at hub.docker.com/metabase/metabase and all non-enterprise paths at downloads.metabase.com are released under the AGPL.
diff --git a/README.md b/README.md
index 3c917c5535a5d2434200dc148b47331ee99869ec..916db6142dacc25b7665e39b613da76738e3ccf2 100644
--- a/README.md
+++ b/README.md
@@ -102,8 +102,8 @@ Metabase also allows you to hit our Query API directly from Javascript to integr
 
 # License
 
-Unless otherwise noted, all Metabase source files are made available under the terms of the GNU Affero General Public License (AGPL).
+This repository contains the source code for both the Open Source edition of Metabase, released under the AGPL, as well as the commercial edition of Metabase Enterprise, released under the Metabase Commercial Software License. 
 
-See [LICENSE.txt](https://github.com/metabase/metabase/blob/master/LICENSE.txt) for details and exceptions.
+See [LICENSE.txt](./LICENSE.txt) for details.
 
 Unless otherwise noted, all files © 2020 Metabase, Inc.
diff --git a/bin/aws-eb-docker/release-eb-version.sh b/bin/aws-eb-docker/release-eb-version.sh
index 1eece8403e7348ea015dabdb8cbb6b319a742495..29e2b0c5c64e29d4a8a81a61e4cc34b777938471 100755
--- a/bin/aws-eb-docker/release-eb-version.sh
+++ b/bin/aws-eb-docker/release-eb-version.sh
@@ -3,7 +3,12 @@
 BASEDIR=$(dirname $0)
 CURRENTDIR=$PWD
 
-DOCKERHUB_REPO=metabase
+if [ "$MB_EDITION" = "ENTERPRISE" ]; then
+    DOCKERHUB_REPO=metabase-enterprise
+else
+    DOCKERHUB_REPO=metabase
+fi
+
 S3_BUCKET=downloads.metabase.com
 
 
@@ -22,8 +27,8 @@ fi
 # TODO: improve this (hard coding)
 EB_FILE=/tmp/${MB_TAG}.zip
 EB_LAUNCH_FILE=launch-aws-eb.html
-S3_FILE=s3://${S3_BUCKET}/${MB_TAG}/metabase-aws-eb.zip
-S3_LAUNCH_FILE=s3://${S3_BUCKET}/${MB_TAG}/${EB_LAUNCH_FILE}
+S3_FILE=s3://${S3_BUCKET}/enterprise/${MB_TAG}/metabase-aws-eb.zip
+S3_LAUNCH_FILE=s3://${S3_BUCKET}/enterprise/${MB_TAG}/${EB_LAUNCH_FILE}
 
 # make the release file
 ${BASEDIR}/build-eb-version.sh ${MB_TAG} ${DOCKERHUB_REPO}
diff --git a/bin/build b/bin/build
index 6ec4fee5c76af45315a58aea77da614111f64469..081205371a4d8b4a873fa2d6fbb4d37a2e4ecb9e 100755
--- a/bin/build
+++ b/bin/build
@@ -2,9 +2,11 @@
 
 set -e
 
+MB_EDITION=${MB_EDITION:=oss}
+
 # Generate the resources/version.properties file
 version() {
-    VERSION_INFO=$(./bin/version)
+    VERSION_INFO=$(./bin/version $MB_EDITION)
     IFS=', ' read -a info <<< ${VERSION_INFO}
 
     echo "Tagging uberjar with version '$VERSION_INFO'..."
@@ -48,8 +50,14 @@ drivers() {
 }
 
 uberjar() {
-    echo "Running 'lein uberjar'..."
-    lein clean && lein uberjar
+    lein clean
+    if [ "$MB_EDITION" = "ENTERPRISE" ]; then
+        echo "Running 'lein with-profile +ee uberjar'..."
+        lein with-profile +ee uberjar
+    else
+        echo "Running 'lein uberjar'..."
+        lein uberjar
+    fi
 }
 
 all() {
diff --git a/bin/docker/build_image.sh b/bin/docker/build_image.sh
index a8d728573e703655a86ee05979b8c3199e52713a..9cb9ea763b140b3399475f3a7340be6c598c7951 100755
--- a/bin/docker/build_image.sh
+++ b/bin/docker/build_image.sh
@@ -37,13 +37,18 @@ fi
 
 
 if [ "$BUILD_TYPE" == "release" ]; then
-    DOCKERHUB_REPOSITORY=metabase
+    if [ "$MB_EDITION" = "ENTERPRISE" ]; then
+        DOCKERHUB_REPO=metabase-enterprise
+    else
+        DOCKERHUB_REPO=metabase
+    fi
+
     DOCKER_IMAGE="${DOCKERHUB_NAMESPACE}/${DOCKERHUB_REPOSITORY}:${MB_TAG}"
 
     echo "Building Docker image ${DOCKER_IMAGE} from official Metabase release ${MB_TAG}"
 
     # download the official version of Metabase which matches our tag
-    curl -L -f -o ${BASEDIR}/metabase.jar https://downloads.metabase.com/${MB_TAG}/metabase.jar
+    curl -L -f -o ${BASEDIR}/metabase.jar https://downloads.metabase.com/enterprise/${MB_TAG}/metabase.jar
 
     if [[ $? -ne 0 ]]; then
         echo "Download failed!"
diff --git a/bin/i18n/update-translation-template b/bin/i18n/update-translation-template
index 62457b6404e85b1251e93abfa1291533f8c5135a..4a4b979dbe4aaad2c1ea10512945486ab9e4a1bf 100755
--- a/bin/i18n/update-translation-template
+++ b/bin/i18n/update-translation-template
@@ -27,7 +27,7 @@ mkdir -p "locales"
 #######################
 
 # NOTE: about twice as fast to call babel directly rather than a full webpack build
-BABEL_ENV=extract ./node_modules/.bin/babel -q -x .js,.jsx -o /dev/null frontend/src
+BABEL_ENV=extract ./node_modules/.bin/babel -q -x .js,.jsx -o /dev/null {enterprise/,}frontend/src
 # BABEL_ENV=extract BABEL_DISABLE_CACHE=1 yarn run build
 
 # NOTE: replace ttag's "${ 0 }" style references with xgettext "{0}" style references for consistency
@@ -43,7 +43,7 @@ rm "$POT_FRONTEND_NAME.bak"
 # info on those systems, and only file names where xgettext supports it
 LOC_OPT=$(xgettext --add-location=file -f - </dev/null >/dev/null 2>&1 && echo --add-location=file || echo --add-location)
 
-find src -name "*.clj" | xgettext                   \
+find . -name "*.clj" | xgettext                     \
   --from-code=UTF-8                                 \
   --language=lisp                                   \
   --copyright-holder='Metabase <docs@metabase.com>' \
diff --git a/bin/release/release/uberjar.clj b/bin/release/release/uberjar.clj
index be9a433bedc89370f4df7ba12b21434aa73c1400..1a41f27cb8dc3f7047c316664624f3c91fdad1eb 100644
--- a/bin/release/release/uberjar.clj
+++ b/bin/release/release/uberjar.clj
@@ -19,16 +19,19 @@
    (s3-artifact-path (c/version) filename))
 
   ([version filename]
-   (format "/%s/%s"
-           (if (= version "latest") "latest" (str "v" version))
-           filename)))
+   (str
+    (when (= (c/edition) :ee)
+      "/enterprise")
+    (format "/%s/%s"
+            (if (= version "latest") "latest" (str "v" version))
+            filename))))
 
 (defn- s3-artifact-url
   ([filename]
    (s3-artifact-url (c/version) filename))
 
   ([version filename]
-   (format "s3://%s%s" (c/downloads-url) (s3-artifact-path version filename))))
+   (format "s3://downloads.metabase.com%s" (s3-artifact-path version filename))))
 
 (defn- update-version-info! []
   (u/step (format "Update %s if needed" (u/assert-file-exists bin-version-file))
diff --git a/bin/test-load-and-dump.sh b/bin/test-load-and-dump.sh
index e56785143fc8881b43269d88f22ce55d9182f2ab..8f09770eb15bf21198dd7fc4f7e7de250a42d4e2 100755
--- a/bin/test-load-and-dump.sh
+++ b/bin/test-load-and-dump.sh
@@ -5,26 +5,28 @@ set -eou pipefail xtrace
 SOURCE_DB="$(pwd)/frontend/test/__runner__/test_db_fixture.db"
 DEST_DB="$(pwd)/dump.db"
 
+MB_EDTION=${MB_EDITION:=oss}
+
 echo -e "\n********************************************************************************"
 echo "Migrating $SOURCE_DB..."
 echo -e "********************************************************************************\n"
 
-MB_DB_TYPE=h2 MB_DB_FILE="$SOURCE_DB" lein run migrate up
+MB_DB_TYPE=h2 MB_DB_FILE="$SOURCE_DB" lein with-profiles +$MB_EDITION run migrate up
 
 echo -e "\n********************************************************************************"
 echo "Loading data from H2 $SOURCE_DB into Postgres/MySQL..."
 echo -e "********************************************************************************\n"
 
-lein run load-from-h2 "$SOURCE_DB"
+lein with-profiles +$MB_EDITION run load-from-h2 "$SOURCE_DB"
 
 echo -e "\n********************************************************************************"
 echo "Dumping data from Postgres/MySQL into H2 $DEST_DB..."
 echo -e "********************************************************************************\n"
 
-lein run dump-to-h2 "$DEST_DB"
+lein with-profiles +$MB_EDITION run dump-to-h2 "$DEST_DB"
 
 echo -e "\n********************************************************************************"
 echo "Comparing contents of $SOURCE_DB and $DEST_DB..."
 echo -e "********************************************************************************\n"
 
-lein compare-h2-dbs "$SOURCE_DB" "$DEST_DB"
+lein with-profiles +$MB_EDITION compare-h2-dbs "$SOURCE_DB" "$DEST_DB"
diff --git a/bin/verify-doc-links b/bin/verify-doc-links
index 0ddbc8b28c64d7512e6e74c8c81f15f8ecf1bc02..cbaae21aea6c7389132bb96e32bb74caf7d71fbf 100755
--- a/bin/verify-doc-links
+++ b/bin/verify-doc-links
@@ -3,7 +3,7 @@
 const glob = require("glob");
 const fs = require("fs");
 
-glob("./frontend/src/metabase/**/*.{js,jsx}", (err, files) => {
+glob("./{enterprise/,}frontend/src/**/*.{js,jsx}", (err, files) => {
   if (err) {
     console.log(err);
     process.exit(1);
diff --git a/bin/version b/bin/version
index ce4f1de2108d8eac8a56f520b359cd88be91a324..d7a3d4b30bad24f3810d9859d6b4648f9d477239 100755
--- a/bin/version
+++ b/bin/version
@@ -1,12 +1,11 @@
 #!/usr/bin/env bash
 
-VERSION='v0.37.0.1'
+VERSION='v1.37.0'
 
 # dynamically pull more interesting stuff from latest git commit
 HASH=$(git show-ref --head --hash=7 head)            # first 7 letters of hash should be enough; that's what GitHub uses
 BRANCH=$(git rev-parse --abbrev-ref HEAD)
 DATE=$(git log -1 --pretty=%ad --date=short)
 
-
 # Return the version string used to describe this version of Metabase.
 echo "$VERSION $HASH $BRANCH $DATE"
diff --git a/docs/enterprise-guide/authenticating-with-saml.md b/docs/enterprise-guide/authenticating-with-saml.md
index a9251e7d0f50b9220cb9fce1d74853a83970ffa0..0fbaa493f3b4153c0b90102c46330426ddcf7be2 100644
--- a/docs/enterprise-guide/authenticating-with-saml.md
+++ b/docs/enterprise-guide/authenticating-with-saml.md
@@ -111,8 +111,22 @@ Metabase will now need to know some things about your IdP. Here's a breakdown of
 | -------- | ------------------------------------ |
 | Auth0    | Identity Provider Login URL          |
 | Okta     | Identity Provider Single-Sign On URL |
+| OneLogin | SAML 2.0 Endpoint (HTTP)             |
+
+**SAML Identity Provider Issuer** This is a unique identifier for the IdP. You might also see it referred to as
+"Entity ID" or "Issuer". Assertions from the IdP will contain this information, and Metabase will verify that it
+matches the value you set. Metabase does not require you to set this value, but it makes your SAML configuration more
+secure, so we recommend that you set it.
+
+Your IdP may label it a little differently. Here are some of the names we've found:
+
+| Provider | Name                                 |
+| -------- | ------------------------------------ |
+| Auth0    | Identity Provider Login URL          |
+| Okta     | Identity Provider Issuer             |
 | OneLogin | Issuer URL                           |
 
+
 **SAML Identity Provider Certificate:** This is an encoded certificate that Metabase will use when connecting to the IdP URI. This will look like a big blob of text that you'll want to copy and paste carefully — the spacing is important! Your IdP might have you download this certificate as a file, which you'll then need to open up in a text editor in order to copy the contents to then paste into the box in Metabase. Again, different providers may have slightly different labels for this:
 
 | Provider | Name                |
diff --git a/enterprise/LICENSE.txt b/enterprise/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..aea9597c69129f62679cd88536aed01567024ed9
--- /dev/null
+++ b/enterprise/LICENSE.txt
@@ -0,0 +1,4 @@
+Usage of files in this directory and its subdirectories, and of Metabase Enterprise Edition features, is subject to
+the Metabase Commercial License (https://www.metabase.com/license/commercial/), and conditional on having a
+valid license from Metabase. Access to files in this directory and its subdirectories does not constitute
+permission to use this code or Metabase Enterprise Edition features.
diff --git a/enterprise/README.md b/enterprise/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..77422b2c03e6ada18146bf0ccb082135fd32b4dd
--- /dev/null
+++ b/enterprise/README.md
@@ -0,0 +1,38 @@
+# Metabase Enterprise Edition
+
+## License
+
+Usage of files in this directory and its subdirectories, and of Metabase Enterprise Edition features, is subject to
+the [Metabase Commercial License](https://www.metabase.com/license/commercial/), and conditional on having a
+fully-paid-up license from Metabase. Access to files in this directory and its subdirectories does not constitute
+permission to use this code or Metabase Enterprise Edition features.
+
+Unless otherwise noted, all files Copyright © 2020 Metabase, Inc.
+
+## Running it
+
+You need to add the `:ee` profile to the leiningen command to run Metabase Enterprise Edition.
+
+```clj
+lein with-profile +ee run
+```
+
+```clj
+lein with-profile +ee uberjar
+```
+
+```clj
+lein with-profile +ee repl
+```
+
+In Emacs/CIDER you can customize the `lein repl` command used to start the REPL by passing a prefix argument, e.g.
+
+```emacs-lisp
+C-u M-x cider-jack-in
+```
+
+or, programatically:
+
+```emacs-lisp
+(cider-jack-in '(4))
+```
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/common.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/common.clj
new file mode 100644
index 0000000000000000000000000000000000000000..6cfd840f149decf32f9a582cd805ca301859c78b
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/common.clj
@@ -0,0 +1,259 @@
+(ns metabase-enterprise.audit.pages.common
+  "Shared functions used by audit internal queries across different namespaces."
+  (:require [clojure
+             [string :as str]
+             [walk :as walk]]
+            [clojure.core
+             [async :as a]
+             [memoize :as memoize]]
+            [clojure.java.jdbc :as jdbc]
+            [honeysql
+             [core :as hsql]
+             [format :as hformat]
+             [helpers :as h]]
+            [medley.core :as m]
+            [metabase
+             [db :as mdb]
+             [driver :as driver]
+             [util :as u]]
+            [metabase-enterprise.audit.query-processor.middleware.handle-audit-queries :as qp.middleware.audit]
+            [metabase.driver.sql-jdbc
+             [connection :as sql-jdbc.conn]
+             [execute :as sql-jdbc.execute]]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.query-processor
+             [context :as context]
+             [timezone :as qp.tz]]
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [urls :as urls]]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(def ^:private ^:const default-limit 1000)
+
+(defn- add-default-params [honeysql-query]
+  (let [{:keys [limit offset]} qp.middleware.audit/*additional-query-params*]
+    (-> honeysql-query
+        (update :limit (fn [query-limit]
+                         (or limit query-limit default-limit)))
+        (update :offset (fn [query-offset]
+                          (or offset query-offset 0))))))
+
+(defn- inject-cte-body-into-from
+  [from ctes]
+  (vec
+   (for [source from]
+     (if (vector? source)
+       (let [[source alias] source]
+         [(ctes source source) alias])
+       (if (ctes source)
+         [(ctes source) source]
+         source)))))
+
+(defn- inject-cte-body-into-join
+  [joins ctes]
+  (->> joins
+       (partition 2)
+       (mapcat (fn [[source condition]]
+                 (if (vector? source)
+                   (let [[source alias] source]
+                     [(if (ctes source)
+                        [(ctes source) alias]
+                        [source alias])
+                      condition])
+                   [(if (ctes source)
+                      [(ctes source) source]
+                      source)
+                    condition])))
+       vec))
+
+(defn- CTEs->subselects
+  ([query] (CTEs->subselects query {}))
+  ([{:keys [with] :as query} ctes]
+   (let [ctes (reduce (fn [ctes [alias definition]]
+                        (assoc ctes alias (CTEs->subselects definition ctes)))
+                      ctes
+                      with)]
+     (walk/postwalk
+      (fn [form]
+        (if (map? form)
+          (-> form
+              (m/update-existing :from inject-cte-body-into-from ctes)
+              ;; TODO -- make this work with all types of joins
+              (m/update-existing :left-join inject-cte-body-into-join ctes)
+              (m/update-existing :join inject-cte-body-into-join ctes))
+          form))
+      (dissoc query :with)))))
+
+(def ^:private ^{:arglists '([])} application-db-default-timezone
+  ;; cache the application DB's default timezone for an hour. I don't expect this information to change *ever*,
+  ;; really, but it seems like it is possible that it *could* change. Determining this for every audit query seems
+  ;; wasteful however.
+  (memoize/ttl
+   (fn []
+     (let [driver (mdb/db-type)]
+       ;; we're using a driver method to determine this, so we need to create a fake DB to get things to work
+       ;; correctly, since various driver impls probably try to use the connection pool to determine this.
+       (driver/db-default-timezone
+        driver
+        {:details @mdb/db-connection-details
+         :engine  driver
+         :id      sql-jdbc.conn/application-db-mock-id})))
+   :ttl/threshold (u/hours->ms 1)))
+
+(defn- reduce-results* [honeysql-query context rff init]
+  (let [driver         (mdb/db-type)
+        honeysql-query (cond-> honeysql-query
+                         ;; MySQL 5.x does not support CTEs, so convert them to subselects instead
+                         (= driver :mysql) CTEs->subselects)
+        [sql & params] (db/honeysql->sql (add-default-params honeysql-query))
+        canceled-chan  (context/canceled-chan context)]
+    ;; MySQL driver normalizies timestamps. Setting `*results-timezone-id-override*` is a shortcut
+    ;; instead of mocking up a chunk of regular QP pipeline.
+    (binding [qp.tz/*results-timezone-id-override* (application-db-default-timezone)]
+      (try
+        (with-open [conn (jdbc/get-connection (db/connection))
+                    stmt (sql-jdbc.execute/prepared-statement driver conn sql params)
+                    rs   (sql-jdbc.execute/execute-query! driver stmt)]
+          (let [rsmeta   (.getMetaData rs)
+                cols     (sql-jdbc.execute/column-metadata driver rsmeta)
+                metadata {:cols cols}
+                rf       (rff metadata)]
+
+            (reduce rf init (sql-jdbc.execute/reducible-rows driver rs rsmeta canceled-chan))))
+        (catch InterruptedException e
+          (a/>!! canceled-chan :cancel)
+          (throw e))))))
+
+(defn reducible-query
+  "Return a function with the signature
+
+    (f context) -> IReduceInit
+
+  that, when reduced, runs `honeysql-query` against the application DB, automatically including limits and offsets for
+  paging."
+  [honeysql-query]
+  (bound-fn reducible-query-fn [context]
+    (reify clojure.lang.IReduceInit
+      (reduce [_ rf init]
+        (reduce-results* honeysql-query context (constantly rf) init)))))
+
+(defn query
+  "Run a internal audit query, automatically including limits and offsets for paging. This function returns results
+  directly as a series of maps (the 'legacy results' format as described in
+  `metabase-enterprise.audit.query-processor.middleware.handle-audit-queries.internal-queries`)"
+  [honeysql-query]
+  (let [context {:canceled-chan (a/promise-chan)}
+        rff     (fn [{:keys [cols]}]
+                  (let [col-names (mapv (comp keyword :name) cols)]
+                    ((map (partial zipmap col-names)) conj)))]
+    (try
+      (reduce-results* honeysql-query context rff [])
+      (catch InterruptedException e
+        (a/>!! (:canceled-chan context) ::cancel)
+        (throw e)))))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                   Helper Fns                                                   |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn user-full-name
+  "HoneySQL to grab the full name of a User.
+
+     (user-full-name :u) ;; -> 'Cam Saul'"
+  [user-table]
+  (hx/concat (hsql/qualify user-table :first_name)
+             (hx/literal " ")
+             (hsql/qualify user-table :last_name)))
+
+(def datetime-unit-str->base-type
+  "Map of datetime unit strings (possible params for queries that accept a datetime `unit` param) to the `:base_type` we
+  should use for that column in the results."
+  {"quarter"         :type/Date
+   "day"             :type/Date
+   "hour"            :type/DateTime
+   "week"            :type/Date
+   "default"         :type/DateTime
+   "day-of-week"     :type/Integer
+   "hour-of-day"     :type/Integer
+   "month"           :type/Date
+   "month-of-year"   :type/Integer
+   "day-of-month"    :type/Integer
+   "year"            :type/Integer
+   "day-of-year"     :type/Integer
+   "week-of-year"    :type/Integer
+   "quarter-of-year" :type/Integer
+   "minute-of-hour"  :type/Integer
+   "minute"          :type/DateTime})
+
+(def DateTimeUnitStr
+  "Scheme for a valid QP DateTime unit as a string (the format they will come into the audit QP). E.g. something
+  like `day` or `day-of-week`."
+  (apply s/enum (keys datetime-unit-str->base-type)))
+
+(defn grouped-datetime
+  "Group a datetime expression by `unit` using the appropriate SQL QP `date` implementation for our application
+  database.
+
+    (grouped-datetime :day :timestamp) ;; -> `cast(timestamp AS date)` [honeysql equivalent]"
+  [unit expr]
+  (sql.qp/date (mdb/db-type) (keyword unit) expr))
+
+(defn first-non-null
+  "Build a `CASE` statement that returns the first non-`NULL` of `exprs`."
+  [& exprs]
+  (apply hsql/call :case (mapcat (fn [expr]
+                                   [[:not= expr nil] expr])
+                                 exprs)))
+
+(defn zero-if-null
+  "Build a `CASE` statement that will replace results of `expr` with `0` when it's `NULL`, perfect for things like
+  counts."
+  [expr]
+  (hsql/call :case [:not= expr nil] expr :else 0))
+
+
+(defn add-search-clause
+  "Add an appropriate `WHERE` clause to `query` to see if any of the `fields-to-search` match `query-string`.
+
+    (add-search-clause {} \"birds\" :t.name :db.name)"
+  [query query-string & fields-to-search]
+  (h/merge-where query (when (seq query-string)
+                         (let [query-string (str \% (str/lower-case query-string) \%)]
+                           (cons
+                            :or
+                            (for [field fields-to-search]
+                              [:like (keyword (str "%lower." (name field))) query-string]))))))
+
+(defn card-public-url
+  "Return HoneySQL for a `CASE` statement to return a Card's public URL if the `public_uuid` `field` is non-NULL."
+  [field]
+  (hsql/call :case
+    [:not= field nil]
+    (hx/concat (urls/public-card-prefix) field)))
+
+(defn native-or-gui
+  "Return HoneySQL for a `CASE` statement to format the QueryExecution `:native` column as either `Native` or `GUI`."
+  [query-execution-table]
+  (hsql/call :case [:= (hsql/qualify query-execution-table :native) true] (hx/literal "Native") :else (hx/literal "GUI")))
+
+(defn card-name-or-ad-hoc
+  "HoneySQL for a `CASE` statement to return the name of a Card, or `Ad-hoc` if Card name is `NULL`."
+  [card-table]
+  (first-non-null (hsql/qualify card-table :name) (hx/literal "Ad-hoc")))
+
+(defn query-execution-is-download
+  "HoneySQL for a `WHERE` clause to restrict QueryExecution rows to downloads (i.e. executions returned in CSV/JSON/XLS
+  format)."
+  [query-execution-table]
+  [:in (hsql/qualify query-execution-table :context) #{"csv-download" "xlsx-download" "json-download"}])
+
+(defn group-concat
+  "Portable MySQL `group_concat`/Postgres `string_agg`"
+  [expr separator]
+  (if (= (mdb/db-type) :mysql)
+    (hsql/call :group_concat (hsql/raw (format "%s SEPARATOR %s"
+                                               (hformat/to-sql expr)
+                                               (hformat/to-sql (hx/literal separator)))))
+    (hsql/call :string_agg expr (hx/literal separator))))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/common/card_and_dashboard_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/common/card_and_dashboard_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..d813e11f8a9bfde07dee07274ffdff669293f121
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/common/card_and_dashboard_detail.clj
@@ -0,0 +1,70 @@
+(ns metabase-enterprise.audit.pages.common.card-and-dashboard-detail
+  "Common queries used by both Card (Question) and Dashboard detail pages."
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase.models
+             [card :refer [Card]]
+             [dashboard :refer [Dashboard]]
+             [revision :as revision]]
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [schema :as su]]
+            [schema.core :as s]))
+
+(def ^:private ModelName
+  (s/enum "card" "dashboard"))
+
+;; SELECT {{group-fn(timestamp}} AS "date", count(*) AS views
+;; FROM view_log
+;; WHERE model = {{model}}
+;;   AND model_id = {{model-id}}
+;; GROUP BY {{group-fn(timestamp}}
+;; ORDER BY {{group-fn(timestamp}} ASC
+(s/defn views-by-time
+  "Get views of a Card or Dashboard broken out by a time `unit`, e.g. `day` or `day-of-week`."
+  [model :- ModelName, model-id :- su/IntGreaterThanZero, unit :- common/DateTimeUnitStr]
+  {:metadata [[:date  {:display_name "Date",  :base_type (common/datetime-unit-str->base-type unit)}]
+              [:views {:display_name "Views", :base_type :type/Integer}]]
+   :results (let [grouped-timestamp (common/grouped-datetime unit :timestamp)]
+              (common/reducible-query
+               {:select   [[grouped-timestamp :date]
+                           [:%count.* :views]]
+                :from     [:view_log]
+                :where    [:and
+                           [:= :model (hx/literal model)]
+                           [:= :model_id model-id]]
+                :group-by [grouped-timestamp]
+                :order-by [[grouped-timestamp :asc]]}))})
+
+(s/defn revision-history
+  "Get a revision history table for a Card or Dashboard."
+  [model-entity :- (s/cond-pre (class Card) (class Dashboard)), model-id :- su/IntGreaterThanZero]
+  {:metadata [[:timestamp   {:display_name "Edited on",   :base_type :type/DateTime}]
+              [:user_id     {:display_name "User ID",     :base_type :type/Integer, :remapped_to   :user_name}]
+              [:user_name   {:display_name "Edited by",   :base_type :type/Name,    :remapped_from :user_id}]
+              [:change_made {:display_name "Change made", :base_type :type/Text}]
+              [:revision_id {:display_name "Revision ID", :base_type :type/Integer}]]
+   :results (for [revision (revision/revisions+details model-entity model-id)]
+              {:timestamp   (-> revision :timestamp)
+               :user_id     (-> revision :user :id)
+               :user_name   (-> revision :user :common_name)
+               :change_made (-> revision :description)
+               :revision_id (-> revision :id)})})
+
+(s/defn audit-log
+  "Get a view log for a Card or Dashboard."
+  [model :- ModelName, model-id :- su/IntGreaterThanZero]
+  {:metadata [[:when    {:display_name "When",    :base_type :type/DateTime}]
+              [:user_id {:display_name "User ID", :base_type :type/Integer, :remapped_to   :who}]
+              [:who     {:display_name "Who",     :base_type :type/Name,    :remapped_from :user_id}]]
+   :results (common/reducible-query
+              {:select    [[:vl.timestamp :when]
+                           :vl.user_id
+                           [(common/user-full-name :u) :who]]
+               :from      [[:view_log :vl]]
+               :join     [[:core_user :u] [:= :vl.user_id :u.id]]
+               :where     [:and
+                           [:= :model (hx/literal model)]
+                           [:= :model_id model-id]]
+               :order-by  [[:vl.timestamp :desc]
+                           [:%lower.u.last_name :asc]
+                           [:%lower.u.first_name :asc]]})})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/common/cards.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/common/cards.clj
new file mode 100644
index 0000000000000000000000000000000000000000..955b93d8feb54a1b0c72e230b4731bd1c5f29ffc
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/common/cards.clj
@@ -0,0 +1,17 @@
+(ns metabase-enterprise.audit.pages.common.cards
+  (:require [metabase.util.honeysql-extensions :as hx]))
+
+(def avg-exec-time
+  "HoneySQL for a CTE to include the average execution time for each Card."
+  [:avg_exec_time {:select   [:card_id
+                              [:%avg.running_time :avg_running_time_ms]]
+                   :from     [:query_execution]
+                   :group-by [:card_id]}])
+
+(def views
+  "HoneySQL for a CTE to include the total view count for each Card."
+  [:card_views {:select   [[:model_id :card_id]
+                           [:%count.* :count]]
+                :from     [:view_log]
+                :where    [:= :model (hx/literal "card")]
+                :group-by [:model_id]}])
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/common/dashboards.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/common/dashboards.clj
new file mode 100644
index 0000000000000000000000000000000000000000..2f70e147ee49a829a2506928a277c9a1d121cada
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/common/dashboards.clj
@@ -0,0 +1,65 @@
+(ns metabase-enterprise.audit.pages.common.dashboards
+  (:require [honeysql
+             [core :as hsql]
+             [helpers :as h]]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [urls :as urls]]))
+
+(defn table
+  "Dashboard table!"
+  [query-string & [where-clause]]
+  {:metadata [[:dashboard_id              {:display_name "Dashboard ID",         :base_type :type/Integer, :remapped_to :title}]
+              [:title                     {:display_name "Title",                :base_type :type/Title,   :remapped_from :dashboard_id}]
+              [:saved_by_id               {:display_name "Saved by User ID",     :base_type :type/Text,    :remapped_to :saved_by}]
+              [:saved_by                  {:display_name "Saved by",             :base_type :type/Text,    :remapped_from :saved_by_id}]
+              [:saved_on                  {:display_name "Saved on",             :base_type :type/DateTime}]
+              [:last_edited_on            {:display_name "Last edited on",       :base_type :type/DateTime}]
+              [:cards                     {:display_name "Cards",                :base_type :type/Integer}]
+              [:public_link               {:display_name "Public Link",          :base_type :type/URL}]
+              [:average_execution_time_ms {:display_name "Avg. exec. time (ms)", :base_type :type/Decimal}]
+              [:total_views               {:display_name "Total views",          :base_type :type/Integer}]]
+   :results  (common/reducible-query
+               (->
+                {:with      [[:card_count {:select   [:dashboard_id
+                                                      [:%count.* :card_count]]
+                                           :from     [:report_dashboardcard]
+                                           :group-by [:dashboard_id]}]
+                             [:card_avg_execution_time {:select   [:card_id
+                                                                   [:%avg.running_time :avg_running_time]]
+                                                        :from     [:query_execution]
+                                                        :where    [:not= :card_id nil]
+                                                        :group-by [:card_id]}]
+                             [:avg_execution_time {:select    [:dc.dashboard_id
+                                                               [:%avg.cxt.avg_running_time :avg_running_time]]
+                                                   :from      [[:report_dashboardcard :dc]]
+                                                   :left-join [[:card_avg_execution_time :cxt] [:= :dc.card_id :cxt.card_id]]
+                                                   :group-by  [:dc.dashboard_id]}]
+                             [:views {:select   [[:model_id :dashboard_id]
+                                                 [:%count.* :view_count]]
+                                      :from     [:view_log]
+                                      :where    [:= :model (hx/literal "dashboard")]
+                                      :group-by [:model_id]}]]
+                 :select    [[:d.id :dashboard_id]
+                             [:d.name :title]
+                             [:u.id :saved_by_id]
+                             [(common/user-full-name :u) :saved_by]
+                             [:d.created_at :saved_on]
+                             [:d.updated_at :last_edited_on]
+                             [:cc.card_count :cards]
+                             [(hsql/call :case
+                                [:not= :d.public_uuid nil]
+                                (hx/concat (urls/public-dashboard-prefix) :d.public_uuid))
+                              :public_link]
+                             [:axt.avg_running_time :average_execution_time_ms]
+                             [:v.view_count :total_views]]
+                 :from      [[:report_dashboard :d]]
+                 :left-join [[:core_user :u]            [:= :d.creator_id :u.id]
+                             [:card_count :cc]          [:= :d.id :cc.dashboard_id]
+                             [:avg_execution_time :axt] [:= :d.id :axt.dashboard_id]
+                             [:views :v]                [:= :d.id :v.dashboard_id]]
+                 :order-by  [[:%lower.d.name :asc]
+                             [:dashboard_id :asc]]}
+                (common/add-search-clause query-string :d.name)
+                (h/merge-where where-clause)))})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/dashboard_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/dashboard_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..164f26c0b8459be371ef5fa238afcb781801a6c8
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/dashboard_detail.clj
@@ -0,0 +1,67 @@
+(ns metabase-enterprise.audit.pages.dashboard-detail
+  "Detail page for a single dashboard."
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase-enterprise.audit.pages.common
+             [card-and-dashboard-detail :as card-and-dash-detail]
+             [cards :as cards]]
+            [metabase.models.dashboard :refer [Dashboard]]
+            [metabase.util.schema :as su]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn views-by-time
+  "Get views of a Dashboard broken out by a time `unit`, e.g. `day` or `day-of-week`."
+  [dashboard-id :- su/IntGreaterThanZero, datetime-unit :- common/DateTimeUnitStr]
+  (card-and-dash-detail/views-by-time "dashboard" dashboard-id datetime-unit))
+
+(s/defn ^:internal-query-fn revision-history
+  [dashboard-id :- su/IntGreaterThanZero]
+  (card-and-dash-detail/revision-history Dashboard dashboard-id))
+
+(s/defn ^:internal-query-fn audit-log
+  [dashboard-id :- su/IntGreaterThanZero]
+  (card-and-dash-detail/audit-log "dashboard" dashboard-id))
+
+
+(s/defn ^:internal-query-fn cards
+  [dashboard-id :- su/IntGreaterThanZero]
+  {:metadata [[:card_id             {:display_name "Card ID",              :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name           {:display_name "Title",                :base_type :type/Name,    :remapped_from :card_id}]
+              [:collection_id       {:display_name "Collection ID",        :base_type :type/Integer, :remapped_to   :collection_name}]
+              [:collection_name     {:display_name "Collection",           :base_type :type/Text,    :remapped_from :collection_id}]
+              [:created_at          {:display_name  "Created At",          :base_type :type/DateTime}]
+              [:database_id         {:display_name "Database ID",          :base_type :type/Integer, :remapped_to   :database_name}]
+              [:database_name       {:display_name "Database",             :base_type :type/Text,    :remapped_from :database_id}]
+              [:table_id            {:display_name "Table ID",             :base_type :type/Integer, :remapped_to   :table_name}]
+              [:table_name          {:display_name "Table",                :base_type :type/Text,    :remapped_from :table_id}]
+              [:avg_running_time_ms {:display_name "Avg. exec. time (ms)", :base_type :type/Number}]
+              [:cache_ttl           {:display_name "Cache TTL",            :base_type :type/Number}]
+              [:public_link         {:display_name "Public Link",          :base_type :type/URL}]
+              [:total_views         {:display_name "Total Views",          :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with      [[:card {:select [:card.*
+                                            [:dc.created_at :dashcard_created_at]]
+                                   :from   [[:report_dashboardcard :dc]]
+                                   :join   [[:report_card :card] [:= :card.id :dc.card_id]]
+                                   :where  [:= :dc.dashboard_id dashboard-id]}]
+                           cards/avg-exec-time
+                           cards/views]
+               :select    [[:card.id :card_id]
+                           [:card.name :card_name]
+                           [:coll.id :collection_id]
+                           [:coll.name :collection_name]
+                           [:card.dashcard_created_at :created_at]
+                           :card.database_id
+                           [:db.name :database_name]
+                           :card.table_id
+                           [:t.name :table_name]
+                           :avg_exec_time.avg_running_time_ms
+                           [(common/card-public-url :card.public_uuid) :public_link]
+                           :card.cache_ttl
+                           [:card_views.count :total_views]]
+               :from      [:card]
+               :left-join [:avg_exec_time           [:= :card.id :avg_exec_time.card_id]
+                           [:metabase_database :db] [:= :card.database_id :db.id]
+                           [:metabase_table :t]     [:= :card.table_id :t.id]
+                           [:collection :coll]      [:= :card.collection_id :coll.id]
+                           :card_views              [:= :card.id :card_views.card_id]]
+               :order-by  [[:%lower.card.name :asc]]})})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/dashboards.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/dashboards.clj
new file mode 100644
index 0000000000000000000000000000000000000000..9ace5fdc09a225ff651c262cb77383323bb56858
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/dashboards.clj
@@ -0,0 +1,153 @@
+(ns metabase-enterprise.audit.pages.dashboards
+  "Dashboards overview page."
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase-enterprise.audit.pages.common.dashboards :as dashboards]
+            [metabase.util.honeysql-extensions :as hx]
+            [schema.core :as s]))
+
+(defn ^:deprecated ^:internal-query-fn views-per-day
+  "DEPRECATED: use `views-and-saves-by-time ` instead."
+  []
+  {:metadata [[:day   {:display_name "Date",  :base_type :type/Date}]
+              [:views {:display_name "Views", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select   [[(hx/cast :date :timestamp) :day]
+                          [:%count.* :views]]
+               :from     [:view_log]
+               :where    [:= :model (hx/literal "dashboard")]
+               :group-by [(hx/cast :date :timestamp)]
+               :order-by [(hx/cast :date :timestamp)]})})
+
+
+(s/defn ^:internal-query-fn views-and-saves-by-time
+  "Two-series timeseries that includes total number of Dashboard views and saves broken out by a `datetime-unit`."
+  [datetime-unit :- common/DateTimeUnitStr]
+  {:metadata [[:date  {:display_name "Date",  :base_type (common/datetime-unit-str->base-type datetime-unit)}]
+              [:views {:display_name "Views", :base_type :type/Integer}]
+              [:saves {:display_name "Saves", :base_type :type/Integer}]]
+   ;; this is so nice and easy to implement in a single query with FULL OUTER JOINS but unfortunately only pg supports
+   ;; them(!)
+   :results (let [views        (common/query
+                                 {:select   [[(common/grouped-datetime datetime-unit :timestamp) :date]
+                                             [:%count.* :views]]
+                                  :from     [:view_log]
+                                  :where    [:= :model (hx/literal "dashboard")]
+                                  :group-by [(common/grouped-datetime datetime-unit :timestamp)]})
+                  date->views  (zipmap (map :date views) (map :views views))
+                  saves        (common/query
+                                 {:select   [[(common/grouped-datetime datetime-unit :created_at) :date]
+                                             [:%count.* :saves]]
+                                  :from     [:report_dashboard]
+                                  :group-by [(common/grouped-datetime datetime-unit :created_at)]})
+                  date->saves  (zipmap (map :date saves) (map :saves saves))
+                  all-dates    (sort (keep identity (distinct (concat (keys date->views)
+                                                                      (keys date->saves)))))]
+              (for [date all-dates]
+                {:date date
+                 :views (date->views date 0)
+                 :saves (date->saves date 0)}))})
+
+
+(defn ^:internal-query-fn ^:deprecated most-popular
+  "Deprecated: use `most-popular-with-avg-speed` instead."
+  []
+  {:metadata [[:dashboard_id   {:display_name "Dashboard ID", :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name {:display_name "Dashboard",    :base_type :type/Title,   :remapped_from :dashboard_id}]
+              [:views          {:display_name "Views",        :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select    [[:d.id :dashboard_id]
+                           [:d.name :dashboard_name]
+                           [:%count.* :views]]
+               :from      [[:view_log :vl]]
+               :left-join [[:report_dashboard :d] [:= :vl.model_id :d.id]]
+               :where     [:= :vl.model (hx/literal "dashboard")]
+               :group-by  [:d.id]
+               :order-by  [[:%count.* :desc]]
+               :limit     10})})
+
+(defn ^:internal-query-fn most-popular-with-avg-speed
+  "10 most popular dashboards with their average speed."
+  []
+  {:metadata [[:dashboard_id     {:display_name "Dashboard ID",                 :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name   {:display_name "Dashboard",                    :base_type :type/Title,   :remapped_from :dashboard_id}]
+              [:views            {:display_name "Views",                        :base_type :type/Integer}]
+              [:avg_running_time {:display_name "Avg. Question Load Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+               {:with      [[:most_popular {:select    [[:d.id :dashboard_id]
+                                                        [:d.name :dashboard_name]
+                                                        [:%count.* :views]]
+                                            :from      [[:view_log :vl]]
+                                            :left-join [[:report_dashboard :d] [:= :vl.model_id :d.id]]
+                                            :where     [:= :vl.model (hx/literal "dashboard")]
+                                            :group-by  [:d.id]
+                                            :order-by  [[:%count.* :desc]]
+                                            :limit     10}]
+                            [:card_running_time {:select   [:qe.card_id
+                                                            [:%avg.qe.running_time :avg_running_time]]
+                                                 :from     [[:query_execution :qe]]
+                                                 :where    [:not= :qe.card_id nil]
+                                                 :group-by [:qe.card_id]}]
+                            [:dash_avg_running_time {:select    [[:d.id :dashboard_id]
+                                                                 [:%avg.rt.avg_running_time :avg_running_time]]
+                                                     :from      [[:report_dashboardcard :dc]]
+                                                     :left-join [[:card_running_time :rt] [:= :dc.card_id :rt.card_id]
+                                                                 [:report_dashboard :d]   [:= :dc.dashboard_id :d.id]]
+                                                     :group-by  [:d.id]
+                                                     :where     [:in :d.id {:select [:dashboard_id]
+                                                                            :from   [:most_popular]}]}]]
+                :select    [:mp.dashboard_id
+                            :mp.dashboard_name
+                            :mp.views
+                            :rt.avg_running_time]
+                :from      [[:most_popular :mp]]
+                :left-join [[:dash_avg_running_time :rt] [:= :mp.dashboard_id :rt.dashboard_id]]
+                :order-by  [[:mp.views :desc]]
+                :limit     10})})
+
+
+(defn ^:internal-query-fn ^:deprecated slowest
+  "Query that returns the 10 Dashboards that have the slowest average execution times, in descending order."
+  []
+  {:metadata [[:dashboard_id     {:display_name "Dashboard ID",                 :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name   {:display_name "Dashboard",                    :base_type :type/Title,   :remapped_from :dashboard_id}]
+              [:avg_running_time {:display_name "Avg. Question Load Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+              {:with      [[:card_running_time {:select   [:qe.card_id
+                                                           [:%avg.qe.running_time :avg_running_time]]
+                                                :from     [[:query_execution :qe]]
+                                                :where    [:not= :qe.card_id nil]
+                                                :group-by [:qe.card_id]}]]
+               :select    [[:d.id :dashboard_id]
+                           [:d.name :dashboard_name]
+                           [:%avg.rt.avg_running_time :avg_running_time]]
+               :from      [[:report_dashboardcard :dc]]
+               :left-join [[:card_running_time :rt] [:= :dc.card_id :rt.card_id]
+                           [:report_dashboard :d]   [:= :dc.dashboard_id :d.id]]
+               :group-by  [:d.id]
+               :order-by  [[:avg_running_time :desc]]
+               :limit     10})})
+
+
+(defn ^:internal-query-fn ^:deprecated most-common-questions
+  "Query that returns the 10 Cards that appear most often in Dashboards, in descending order."
+  []
+  {:metadata [[:card_id   {:display_name "Card ID", :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name {:display_name "Card",    :base_type :type/Title,   :remapped_from :card_id}]
+              [:count     {:display_name "Count",   :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select   [[:c.id :card_id]
+                          [:c.name :card_name]
+                          [:%count.* :count]]
+               :from     [[:report_dashboardcard :dc]]
+               :join     [[:report_card :c] [:= :c.id :dc.card_id]]
+               :group-by [:c.id]
+               :order-by [[:%count.* :desc]]
+               :limit    10})})
+
+
+(s/defn ^:internal-query-fn table
+  "Internal audit app query powering a table of different Dashboards with lots of extra info about them."
+  ([]
+   (table nil))
+  ([query-string :- (s/maybe s/Str)]
+   (dashboards/table query-string)))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/database_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/database_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..bed468bd59792cf5fddd9565a0b17fef4530d790
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/database_detail.clj
@@ -0,0 +1,35 @@
+(ns metabase-enterprise.audit.pages.database-detail
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.schema :as su]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn audit-log
+  [database-id :- su/IntGreaterThanZero]
+  {:metadata [[:started_at {:display_name "Viewed on",  :base_type :type/DateTime}]
+              [:card_id    {:display_name "Card ID",    :base_type :type/Integer, :remapped_to   :query}]
+              [:query_hash {:display_name "Query Hash", :base_type :type/Text}]
+              [:query      {:display_name "Query",      :base_type :type/Text,    :remapped_from :card_id}]
+              [:user_id    {:display_name "User ID",    :base_type :type/Integer, :remapped_to   :user}]
+              [:user       {:display_name "Queried by", :base_type :type/Text,    :remapped_from :user_id}]
+              [:schema     {:display_name "Schema",     :base_type :type/Text}]
+              [:table_id   {:display_name "Table ID",   :base_type :type/Integer, :remapped_to   :table}]
+              [:table      {:display_name "Table",      :base_type :type/Text,    :remapped_from :table_id}]]
+   :results (common/reducible-query
+                  {:select    [:qe.started_at
+                               [:card.id :card_id]
+                               [:qe.hash :query_hash]
+                               [(common/card-name-or-ad-hoc :card) :query]
+                               [:u.id :user_id]
+                               [(common/user-full-name :u) :user]
+                               :t.schema
+                               [:t.id :table_id]
+                               [:t.name :table]]
+                   :from      [[:query_execution :qe]]
+                   :where     [:= :qe.database_id database-id]
+                   :join      [[:metabase_database :db] [:= :db.id :qe.database_id]
+                               [:core_user :u] [:= :qe.executor_id :u.id]]
+                   :left-join [[:report_card :card] [:= :qe.card_id :card.id]
+                               [:metabase_table :t] [:= :card.table_id :t.id]]
+                   :order-by  [[:qe.started_at :desc]]})
+   :xform   (map #(update (vec %) 2 codec/base64-encode))})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/databases.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/databases.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e58632c0a184c22cf8a8e70b6ef9dbac57188d3f
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/databases.clj
@@ -0,0 +1,102 @@
+(ns metabase-enterprise.audit.pages.databases
+  (:require [honeysql.core :as hsql]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.cron :as cron]
+            [schema.core :as s]))
+
+;; SELECT
+;;   db.id AS database_id,
+;;   db.name AS database_name,
+;;   count(*) AS queries,
+;;   avg(qe.running_time) AS avg_running_time
+;; FROM query_execution qe
+;; JOIN report_card card     ON qe.card_id = card.id
+;; JOIN metabase_table t     ON card.table_id = t.id
+;; JOIN metabase_database db ON t.db_id = db.id
+;; GROUP BY db.id
+;; ORDER BY lower(db.name) ASC
+(defn ^:internal-query-fn ^:deprecated total-query-executions-by-db
+  "Return Databases with the total number of queries ran against them and the average running time for all queries."
+  []
+  {:metadata [[:database_id      {:display_name "Database ID",            :base_type :type/Integer, :remapped_to   :database_name}]
+              [:database_name    {:display_name "Database",               :base_type :type/Text,    :remapped_from :database_id}]
+              [:queries          {:display_name "Queries",                :base_type :type/Integer}]
+              [:avg_running_time {:display_name "Avg. Running Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+              {:select   [[:db.id :database_id]
+                          [:db.name :database_name]
+                          [:%count.* :queries]
+                          [:%avg.qe.running_time :avg_running_time]]
+               :from     [[:query_execution :qe]]
+               :join     [[:report_card :card]     [:= :qe.card_id :card.id]
+                          [:metabase_table :t]     [:= :card.table_id :t.id]
+                          [:metabase_database :db] [:= :t.db_id :db.id]]
+               :group-by [:db.id]
+               :order-by [[:%lower.db.name :asc]]})})
+
+(s/defn ^:internal-query-fn query-executions-by-time
+  "Query that returns count of query executions grouped by Database and a `datetime-unit`."
+  [datetime-unit :- common/DateTimeUnitStr]
+  {:metadata [[:date          {:display_name "Date",          :base_type (common/datetime-unit-str->base-type datetime-unit)}]
+              [:database_id   {:display_name "Database ID",   :base_type :type/Integer, :remapped_to   :database_name}]
+              [:database_name {:display_name "Database Name", :base_type :type/Name,    :remapped_from :database_id}]
+              [:count         {:display_name "Count",         :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with      [[:qx {:select    [[(common/grouped-datetime datetime-unit :qe.started_at) :date]
+                                             :card.database_id
+                                             [:%count.* :count]]
+                                 :from      [[:query_execution :qe]]
+                                 :left-join [[:report_card :card] [:= :qe.card_id :card.id]]
+                                 :where     [:and
+                                             [:not= :qe.card_id nil]
+                                             [:not= :card.database_id nil]]
+                                 :group-by  [(common/grouped-datetime datetime-unit :qe.started_at) :card.database_id]
+                                 :order-by  [[(common/grouped-datetime datetime-unit :qe.started_at) :asc]
+                                             [:card.database_id :asc]]}]]
+               :select    [:qx.date
+                           :qx.database_id
+                           [:db.name :database_name]
+                           :qx.count]
+               :from      [:qx]
+               :left-join [[:metabase_database :db] [:= :qx.database_id :db.id]]
+               :order-by  [[:qx.date :asc]
+                           [:%lower.db.name :asc]
+                           [:qx.database_id :asc]]})})
+
+(defn ^:deprecated ^:internal-query-fn query-executions-per-db-per-day
+  "Query that returns count of query executions grouped by Database and day."
+  []
+  (query-executions-by-time "day"))
+
+
+(s/defn ^:internal-query-fn table
+  ([]
+   (table nil))
+  ([query-string :- (s/maybe s/Str)]
+   ;; TODO - Should we convert sync_schedule from a cron string into English? Not sure that's going to be feasible for
+   ;; really complicated schedules
+   {:metadata [[:database_id   {:display_name "Database ID", :base_type :type/Integer, :remapped_to :title}]
+               [:title         {:display_name "Title", :base_type :type/Text, :remapped_from :database_id}]
+               [:added_on      {:display_name "Added On", :base_type :type/DateTime}]
+               [:sync_schedule {:display_name "Sync Schedule", :base_type :type/Text}]
+               [:schemas       {:display_name "Schemas", :base_type :type/Integer}]
+               [:tables        {:display_name "Tables", :base_type :type/Integer}]]
+    :results  (common/reducible-query
+               (->
+                {:with      [[:counts {:select   [[:db_id :id]
+                                                  [(hsql/call :distinct-count :schema) :schemas]
+                                                  [:%count.* :tables]]
+                                       :from     [:metabase_table]
+                                       :group-by [:db_id]}]]
+                 :select    [[:db.id :database_id]
+                             [:db.name :title]
+                             [:db.created_at :added_on]
+                             [:db.metadata_sync_schedule :sync_schedule]
+                             [:counts.schemas :schemas]
+                             [:counts.tables :tables]]
+                 :from      [[:metabase_database :db]]
+                 :left-join [:counts [:= :db.id :counts.id]]
+                 :order-by  [[:%lower.db.name :asc]
+                             [:database_id :asc]]}
+                (common/add-search-clause query-string :db.name)))
+    :xform    (map #(update (vec %) 3 cron/describe-cron-string))}))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/downloads.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/downloads.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0d01a99e2504776a4318cfd0eb33e7e9041553b0
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/downloads.clj
@@ -0,0 +1,168 @@
+(ns metabase-enterprise.audit.pages.downloads
+  "Audit queries returning info about query downloads. Query downloads are any query executions whose results are returned
+  as CSV/JSON/XLS."
+  (:require [honeysql.core :as hsql]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase.db :as mdb]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.util.honeysql-extensions :as hx]
+            [schema.core :as s]))
+
+;;; ------------------------------------------------ per-day-by-size -------------------------------------------------
+
+(s/defn ^:internal-query-fn per-day-by-size
+  "Pairs of count of rows downloaded and date downloaded for the 1000 largest (in terms of row count) queries over the
+  past 30 days. Intended to power scatter plot."
+  []
+  {:metadata [[:date      {:display_name "Day",           :base_type :type/DateTime}]
+              [:rows      {:display_name "Rows in Query", :base_type :type/Integer}]
+              [:user_id   {:display_name "User ID",       :base_type :type/Integer, :remapped_to :user_name}]
+              [:user_name {:display_name "User",          :base_type :type/Text,    :remapped_from :user_id}]]
+   :results  (common/reducible-query
+               {:select   [[:qe.started_at :date]
+                           [:qe.result_rows :rows]
+                           [:qe.executor_id :user_id]
+                           [(common/user-full-name :u) :user_name]]
+                :from     [[:query_execution :qe]]
+                :left-join [[:core_user :u] [:= :qe.executor_id :u.id]]
+                :where    [:and
+                           [:> :qe.started_at (sql.qp/add-interval-honeysql-form (mdb/db-type) :%now -30 :day)]
+                           (common/query-execution-is-download :qe)]
+                :order-by [[:qe.result_rows :desc]]
+                :limit    1000})})
+
+
+;;; ---------------------------------------------------- per-user ----------------------------------------------------
+
+(s/defn ^:internal-query-fn per-user
+  "Total count of query downloads broken out by user, ordered by highest total, for the top 10 users."
+  []
+  {:metadata [[:user_id   {:display_name "User ID",   :base_type :type/Integer, :remapped_to :user_name}]
+              [:user_name {:display_name "User",      :base_type :type/Text,    :remapped_from :user_id}]
+              [:downloads {:display_name "Downloads", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+               {:with     [[:downloads_by_user
+                            {:select   [[:qe.executor_id :user_id]
+                                        [:%count.* :downloads]]
+                             :from     [[:query_execution :qe]]
+                             :where    (common/query-execution-is-download :qe)
+                             :group-by [:qe.executor_id]
+                             :order-by [[:%count.* :desc]]
+                             :limit    10}]]
+                :select   [[:d.user_id :user_id]
+                           [(common/user-full-name :u) :user_name]
+                           [:d.downloads :downloads]]
+                :from     [[:downloads_by_user :d]]
+                :join     [[:core_user :u] [:= :d.user_id :u.id]]
+                :order-by [[:d.downloads :desc]]})})
+
+
+;;; ---------------------------------------------------- by-size -----------------------------------------------------
+
+(def ^:private bucket-maxes
+  "Add/remove numbers here to adjust buckets returned by the `by-size` query."
+  [     10
+       100
+      1000
+      5000
+     10000
+     50000
+    100000
+    500000
+   1000000])
+
+(def ^:private rows->bucket-case-expression
+  "`CASE` expression to put `result_rows` in appropriate buckets. Looks something like:
+
+    CASE ... result_rows <= 100 THEN 100 ..."
+  (apply hsql/call :case (concat
+                          (mapcat (fn [bucket-max]
+                                    [[:<= :result_rows bucket-max] bucket-max])
+                                  bucket-maxes)
+                          [:else -1])))
+
+(def ^:private bucket-ranges
+  "Pairs like [[0 10], [11 100], ...]"
+  (reduce
+   (fn [acc bucket-max]
+     (conj acc [(or (some-> acc last last inc) 0) ; get min from last pair in acc or 0
+                bucket-max]))
+   []
+   bucket-maxes))
+
+(defn- format-number-add-commas
+  "Format number to string adding commas as thousands separators."
+  [^Number n]
+  (.format (java.text.DecimalFormat. "#,###") n))
+
+(defn- bucket-range->literal
+  "Given a bucket range pair like [101 1000] return a formatted string including commas like `101-1,000`."
+  [[bucket-min bucket-max]]
+  (hx/literal (format "%s-%s" (format-number-add-commas bucket-min) (format-number-add-commas bucket-max))))
+
+(def ^:private bucket->range-str-case-expression
+  "`CASE` expression to generate range strings for each bucket. Looks something like:
+
+    CASE ... (rows_bucket_max = 1000) THEN '101-1,000' ..."
+  (apply hsql/call :case (concat
+                          (mapcat (fn [[_ bucket-max :as bucket-range]]
+                                    [[:= :rows_bucket_max bucket-max] (bucket-range->literal bucket-range)])
+                                  bucket-ranges)
+                          [[:= :rows_bucket_max -1]
+                           (hx/literal (format "> %s" (format-number-add-commas (last bucket-maxes))))])))
+
+(s/defn ^:internal-query-fn by-size
+  "Query download count broken out by bucketed number of rows of query. E.g. 10 downloads of queries with 0-10 rows, 15
+  downloads of queries with 11-100, etc. Intended to power bar chart."
+  []
+  {:metadata [[:rows      {:display_name "Rows Downloaded", :base_type :type/Text}]
+              [:downloads {:display_name "Downloads",       :base_type :type/Integer}]]
+   :results  (common/reducible-query
+               {:with     [[:bucketed_downloads
+                            {:select [[rows->bucket-case-expression :rows_bucket_max]]
+                             :from   [:query_execution]
+                             :where  [:and
+                                      (common/query-execution-is-download :query_execution)
+                                      [:not= :result_rows nil]]}]]
+                :select   [[bucket->range-str-case-expression :rows]
+                           [:%count.* :downloads]]
+                :from     [:bucketed_downloads]
+                :group-by [:rows_bucket_max]
+                :order-by [[:rows_bucket_max :asc]]})})
+
+
+;;; ----------------------------------------------------- table ------------------------------------------------------
+
+(s/defn ^:internal-query-fn table
+  "Table showing all query downloads ordered by most recent."
+  []
+  {:metadata [[:downloaded_at   {:display_name "Downloaded At",   :base_type :type/DateTime}]
+              [:rows_downloaded {:display_name "Rows Downloaded", :base_type :type/Integer}]
+              [:card_id         {:display_name "Card ID",         :base_type :type/Integer, :remapped_to :card_name}]
+              [:card_name       {:display_name "Query",           :base_type :type/Text,    :remapped_from :card_id}]
+              [:query_type      {:display_name "Query Type",      :base_type :type/Text}]
+              [:database_id     {:display_name "Database ID",     :base_type :type/Integer, :remapped_to :database}]
+              [:database        {:display_name "Database",        :base_type :type/Text,    :remapped_from :database_id}]
+              [:source_table_id {:display_name "Source Table ID", :base_type :type/Integer, :remapped_to :source_table}]
+              [:source_table    {:display_name "Source Table",    :base_type :type/Text,    :remapped_from :source_table_id}]
+              [:user_id         {:display_name "User ID",         :base_type :type/Integer, :remapped_to :user_name}]
+              [:user_name       {:display_name "User",            :base_type :type/Text,    :remapped_from :user_id}]]
+   :results  (common/reducible-query
+              {:select    [[:qe.started_at :downloaded_at]
+                           [:qe.result_rows :rows_downloaded]
+                           [:card.id :card_id]
+                           [(common/card-name-or-ad-hoc :card) :card_name]
+                           [(common/native-or-gui :qe) :query_type]
+                           [:db.id :database_id]
+                           [:db.name :database]
+                           [:t.id :source_table_id]
+                           [:t.name :source_table]
+                           [:qe.executor_id :user_id]
+                           [(common/user-full-name :u) :user_name]]
+               :from      [[:query_execution :qe]]
+               :left-join [[:report_card :card] [:= :card.id :qe.card_id]
+                           [:metabase_database :db] [:= :qe.database_id :db.id]
+                           [:metabase_table :t] [:= :card.table_id :t.id]
+                           [:core_user :u] [:= :qe.executor_id :u.id]]
+               :where     (common/query-execution-is-download :qe)
+               :order-by  [[:qe.started_at :desc]]})})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/queries.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/queries.clj
new file mode 100644
index 0000000000000000000000000000000000000000..873f7f201bbe1328a5036bbb505b1d498ce9cb5d
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/queries.clj
@@ -0,0 +1,98 @@
+(ns metabase-enterprise.audit.pages.queries
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase-enterprise.audit.pages.common.cards :as cards]
+            [metabase.util.honeysql-extensions :as hx]
+            [schema.core :as s]))
+
+(defn ^:internal-query-fn ^:deprecated  views-and-avg-execution-time-by-day
+  "Query that returns data for a two-series timeseries chart with number of queries ran and average query running time
+  broken out by day."
+  []
+  {:metadata [[:day              {:display_name "Date",                   :base_type :type/Date}]
+              [:views            {:display_name "Views",                  :base_type :type/Integer}]
+              [:avg_running_time {:display_name "Avg. Running Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+              {:select   [[(hx/cast :date :started_at) :day]
+                          [:%count.* :views]
+                          [:%avg.running_time :avg_running_time]]
+               :from     [:query_execution]
+               :group-by [(hx/cast :date :started_at)]
+               :order-by [[(hx/cast :date :started_at) :asc]]})})
+
+(defn ^:internal-query-fn most-popular
+  "Query that returns the 10 most-popular Cards based on number of query executions, in descending order."
+  []
+  {:metadata [[:card_id    {:display_name "Card ID",    :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name  {:display_name "Card",       :base_type :type/Title,   :remapped_from :card_id}]
+              [:executions {:display_name "Executions", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select   [[:c.id :card_id]
+                          [:c.name :card_name]
+                          [:%count.* :executions]]
+               :from     [[:query_execution :qe]]
+               :join     [[:report_card :c] [:= :qe.card_id :c.id]]
+               :group-by [:c.id]
+               :order-by [[:executions :desc]]
+               :limit    10})})
+
+(defn ^:internal-query-fn ^:deprecated slowest
+  "Query that returns the 10 slowest-running Cards based on average query execution time, in descending order."
+  []
+  {:metadata [[:card_id          {:display_name "Card ID",                :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name        {:display_name "Card",                   :base_type :type/Title,   :remapped_from :card_id}]
+              [:avg_running_time {:display_name "Avg. Running Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+              {:select   [[:c.id :card_id]
+                          [:c.name :card_name]
+                          [:%avg.running_time :avg_running_time]]
+               :from     [[:query_execution :qe]]
+               :join     [[:report_card :c] [:= :qe.card_id :c.id]]
+               :group-by [:c.id]
+               :order-by [[:avg_running_time :desc]]
+               :limit    10})})
+
+(s/defn ^:internal-query-fn table
+  "A list of all questions."
+  ([]
+   (table nil))
+  ([query-string :- (s/maybe s/Str)]
+   {:metadata [[:card_id         {:display_name "Card ID",       :base_type :type/Integer, :remapped_to   :card_name}]
+               [:card_name       {:display_name "Name",          :base_type :type/Name,    :remapped_from :card_id}]
+               [:collection_id   {:display_name "Collection ID", :base_type :type/Integer, :remapped_to   :collection_name}]
+               [:collection_name {:display_name "Collection",    :base_type :type/Text,    :remapped_from :collection_id}]
+               [:database_id     {:display_name "Database ID",   :base_type :type/Integer, :remapped_to   :database_name}]
+               [:database_name   {:display_name "Database",      :base_type :type/Text,    :remapped_from :database_id}]
+               [:table_id        {:display_name "Table ID",      :base_type :type/Integer, :remapped_to   :table_name}]
+               [:table_name      {:display_name "Table",         :base_type :type/Text,    :remapped_from :table_id}]
+               [:user_id         {:display_name "Created By ID", :base_type :type/Integer, :remapped_to   :user_name}]
+               [:user_name       {:display_name "Created By",    :base_type :type/Text,    :remapped_from :user_id}]
+               [:public_link     {:display_name "Public Link",   :base_type :type/URL}]
+               [:cache_ttl       {:display_name "Cache TTL",     :base_type :type/Number}]
+               [:total_views     {:display_name "Views",         :base_type :type/Integer}]]
+    :results  (common/reducible-query
+                (->
+                 {:with      [cards/avg-exec-time
+                              cards/views]
+                  :select    [[:card.id :card_id]
+                              [:card.name :card_name]
+                              :collection_id
+                              [:coll.name :collection_name]
+                              :card.database_id
+                              [:db.name :database_name]
+                              :card.table_id
+                              [:t.name :table_name]
+                              [:card.creator_id :user_id]
+                              [(common/user-full-name :u) :user_name]
+                              [(common/card-public-url :card.public_uuid) :public_link]
+                              :card.cache_ttl
+                              [:card_views.count :total_views]]
+                  :from      [[:report_card :card]]
+                  :left-join [[:collection :coll]      [:= :card.collection_id :coll.id]
+                              [:metabase_database :db] [:= :card.database_id :db.id]
+                              [:metabase_table :t]     [:= :card.table_id :t.id]
+                              [:core_user :u]          [:= :card.creator_id :u.id]
+                              :avg_exec_time           [:= :card.id :avg_exec_time.card_id]
+                              :card_views              [:= :card.id :card_views.card_id]]
+                  :where     [:= :card.archived false]
+                  :order-by  [[:%lower.card.name :asc]]}
+                 (common/add-search-clause query-string :card.name)))}))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/query_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/query_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..46e190e068c4ebcf0edae5ef43480989521cccba
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/query_detail.clj
@@ -0,0 +1,19 @@
+(ns metabase-enterprise.audit.pages.query-detail
+  "Queries to show details about a (presumably ad-hoc) query."
+  (:require [cheshire.core :as json]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.schema :as su]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn details
+  [query-hash :- su/NonBlankString]
+  {:metadata [[:query                  {:display_name "Query",                :base_type :type/Dictionary}]
+              [:average_execution_time {:display_name "Avg. Exec. Time (ms)", :base_type :type/Number}]]
+   :results  (common/reducible-query
+              {:select [:query
+                        :average_execution_time]
+               :from   [:query]
+               :where  [:= :query_hash (codec/base64-decode query-hash)]
+               :limit  1})
+   :xform (map #(update (vec %) 0 json/parse-string))})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/question_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/question_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..5b62dfea3c8bf27740635e93172fb8c1fe17321d
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/question_detail.clj
@@ -0,0 +1,23 @@
+(ns metabase-enterprise.audit.pages.question-detail
+  "Detail page for a single Card (Question)."
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase-enterprise.audit.pages.common.card-and-dashboard-detail :as card-and-dash-detail]
+            [metabase.models.card :refer [Card]]
+            [metabase.util.schema :as su]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn views-by-time
+  "Get views of a Card broken out by a time `unit`, e.g. `day` or `day-of-week`."
+  [card-id :- su/IntGreaterThanZero, datetime-unit :- common/DateTimeUnitStr]
+  (card-and-dash-detail/views-by-time "card" card-id datetime-unit))
+
+
+(s/defn ^:internal-query-fn revision-history
+  "Get the revision history for a Card."
+  [card-id :- su/IntGreaterThanZero]
+  (card-and-dash-detail/revision-history Card card-id))
+
+(s/defn ^:internal-query-fn audit-log
+  "Get a view log for a Card."
+  [card-id :- su/IntGreaterThanZero]
+  (card-and-dash-detail/audit-log "card" card-id))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/schemas.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/schemas.clj
new file mode 100644
index 0000000000000000000000000000000000000000..276b06964a38cba4e160a6f5371394df1a844283
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/schemas.clj
@@ -0,0 +1,152 @@
+(ns metabase-enterprise.audit.pages.schemas
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.honeysql-extensions :as hx]
+            [schema.core :as s]))
+
+;; WITH counts AS (
+;;     SELECT db."name" AS db_name, t."schema" AS db_schema
+;;     FROM query_execution qe
+;;     LEFT JOIN report_card card
+;;       ON qe.card_id = card.id
+;;     LEFT JOIN metabase_database db
+;;       ON card.database_id = db.id
+;;     LEFT JOIN metabase_table t
+;;       ON card.table_id = t.id
+;;     WHERE qe.card_id IS NOT NULL
+;;       AND card.database_id IS NOT NULL
+;;       AND card.table_id IS NOT NULL
+;; )
+;;
+;; SELECT (db_name || ' ' || db_schema) AS "schema", count(*) AS executions
+;; FROM counts
+;; GROUP BY db_name, db_schema
+;; ORDER BY count(*) DESC
+;; LIMIT 10
+(defn ^:internal-query-fn ^:deprecated most-queried
+  "Query that returns the top 10 most-queried schemas, in descending order."
+  []
+  {:metadata [[:schema     {:display_name "Schema",     :base_type :type/Title}]
+              [:executions {:display_name "Executions", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with     [[:counts {:select    [[:db.name :db_name]
+                                                [:t.schema :db_schema]]
+                                    :from      [[:query_execution :qe]]
+                                    :left-join [[:report_card :card]     [:= :qe.card_id :card.id]
+                                                [:metabase_database :db] [:= :card.database_id :db.id]
+                                                [:metabase_table :t]     [:= :card.table_id :t.id]]
+                                    :where     [:and
+                                                [:not= :qe.card_id nil]
+                                                [:not= :card.database_id nil]
+                                                [:not= :card.table_id nil]]}]]
+               :select   [[(hx/concat :db_name (hx/literal " ") :db_schema) :schema]
+                          [:%count.* :executions]]
+               :from     [:counts]
+               :group-by [:db_name :db_schema]
+               :order-by [[:%count.* :desc]]
+               :limit    10})})
+
+;; WITH counts AS (
+;;     SELECT db."name" AS db_name, t."schema" AS db_schema, qe.running_time
+;;     FROM query_execution qe
+;;     LEFT JOIN report_card card
+;;       ON qe.card_id = card.id
+;;     LEFT JOIN metabase_database db
+;;       ON card.database_id = db.id
+;;     LEFT JOIN metabase_table t
+;;       ON card.table_id = t.id
+;;     WHERE qe.card_id IS NOT NULL
+;;       AND card.database_id IS NOT NULL
+;;       AND card.table_id IS NOT NULL
+;; )
+;;
+;; SELECT (db_name || ' ' || db_schema) AS "schema", avg(running_time) AS avg_running_time
+;; FROM counts
+;; GROUP BY db_name, db_schema
+;; ORDER BY avg_running_time DESC
+;; LIMIT 10
+(defn ^:internal-query-fn ^:deprecated slowest-schemas
+  "Query that returns the top 10 schemas with the slowest average query execution time in descending order."
+  []
+  {:metadata [[:schema           {:display_name "Schema",                    :base_type :type/Title}]
+              [:avg_running_time {:display_name "Average Running Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+              {:with     [[:counts {:select    [[:db.name :db_name]
+                                                [:t.schema :db_schema]
+                                                :qe.running_time]
+                                    :from      [[:query_execution :qe]]
+                                    :left-join [[:report_card :card]     [:= :qe.card_id :card.id]
+                                                [:metabase_database :db] [:= :card.database_id :db.id]
+                                                [:metabase_table :t]     [:= :card.table_id :t.id]]
+                                    :where     [:and
+                                                [:not= :qe.card_id nil]
+                                                [:not= :card.database_id nil]
+                                                [:not= :card.table_id nil]]}]]
+               :select   [[(hx/concat :db_name (hx/literal " ") :db_schema) :schema]
+                          [:%avg.running_time :avg_running_time]]
+               :from     [:counts]
+               :group-by [:db_name :db_schema]
+               :order-by [[:avg_running_time :desc]]
+               :limit    10})})
+
+;; WITH cards AS (
+;;     SELECT t.db_id AS database_id, t."schema", count(*) AS saved_count
+;;     FROM report_card c
+;;     LEFT JOIN metabase_table t
+;;       ON c.table_id = t.id
+;;     WHERE c.table_id IS NOT NULL
+;;     GROUP BY t.db_id, t."schema"
+;; ),
+;;
+;; schemas AS (
+;;     SELECT db.id AS database_id, db.name AS database_name, t."schema", COUNT(*) AS tables
+;;     FROM metabase_table t
+;;     LEFT JOIN metabase_database db
+;;       ON t.db_id = db.id
+;;     GROUP BY db.id, t."schema"
+;;     ORDER BY db.name ASC, t."schema" ASC
+;; )
+;;
+;; SELECT s.database_name AS "database", s."schema", s.tables, c.saved_count AS saved_queries
+;; FROM schemas
+;; LEFT JOIN cards c
+;;   ON s.database_id = c.database_id AND s."schema" = c."schema"
+(s/defn ^:internal-query-fn ^:deprecated table
+  "Query that returns a data for a table full of fascinating information about the different schemas in use in our
+  application."
+  ([]
+   (table nil))
+  ([query-string :- (s/maybe s/Str)]
+   {:metadata [[:database_id   {:display_name "Database ID",   :base_type :type/Integer, :remapped_to   :database}]
+               [:database      {:display_name "Database",      :base_type :type/Title,   :remapped_from :database_id}]
+               [:schema_id     {:display_name "Schema ID",     :base_type :type/Text,    :remapped_to   :schema}]
+               [:schema        {:display_name "Schema",        :base_type :type/Title,   :remapped_from :schema_id}]
+               [:tables        {:display_name "Tables",        :base_type :type/Integer}]
+               [:saved_queries {:display_name "Saved Queries", :base_type :type/Integer}]]
+    :results  (common/reducible-query
+                (->
+                 {:with      [[:cards {:select    [[:t.db_id :database_id]
+                                                   :t.schema
+                                                   [:%count.* :saved_count]]
+                                       :from      [[:report_card :c]]
+                                       :left-join [[:metabase_table :t] [:= :c.table_id :t.id]]
+                                       :where     [:not= :c.table_id nil]
+                                       :group-by  [:t.db_id :t.schema]}]
+                              [:schemas {:select    [[:db.id :database_id]
+                                                     [:db.name :database_name]
+                                                     :t.schema
+                                                     [:%count.* :tables]]
+                                         :from      [[:metabase_table :t]]
+                                         :left-join [[:metabase_database :db] [:= :t.db_id :db.id]]
+                                         :group-by  [:db.id :t.schema]
+                                         :order-by  [[:db.id :asc] [:t.schema :asc]]}]]
+                  :select    [:s.database_id
+                              [:s.database_name :database]
+                              [(hx/concat :s.database_id (hx/literal ".") :s.schema) :schema_id]
+                              :s.schema
+                              :s.tables
+                              [:c.saved_count :saved_queries]]
+                  :from      [[:schemas :s]]
+                  :left-join [[:cards :c] [:and
+                                           [:= :s.database_id :c.database_id]
+                                           [:= :s.schema :c.schema]]]}
+                 (common/add-search-clause query-string :s.schema)))}))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/table_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/table_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..2309d9eb4ae2ab0b4a1c0adcb2671b56afc00164
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/table_detail.clj
@@ -0,0 +1,27 @@
+(ns metabase-enterprise.audit.pages.table-detail
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.schema :as su]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn audit-log
+  [table-id :- su/IntGreaterThanZero]
+  {:metadata [[:started_at {:display_name "Viewed on",  :base_type :type/DateTime}]
+              [:card_id    {:display_name "Card ID",    :base_type :type/Integer, :remapped_to   :query}]
+              [:query      {:display_name "Query",      :base_type :type/Text,    :remapped_from :card_id}]
+              [:query_hash {:display_name "Query Hash", :base_type :type/Text}]
+              [:user_id    {:display_name "User ID",    :base_type :type/Integer, :remapped_to   :user}]
+              [:user       {:display_name "Queried by", :base_type :type/Text,    :remapped_from :user_id}]]
+   :results (common/reducible-query
+             {:select    [:qe.started_at
+                          [:card.id :card_id]
+                          [(common/card-name-or-ad-hoc :card) :query]
+                          [:qe.hash :query_hash]
+                          [:u.id :user_id]
+                          [(common/user-full-name :u) :user]]
+              :from      [[:query_execution :qe]]
+              :where     [:= :card.table_id table-id]
+              :join      [[:core_user :u] [:= :qe.executor_id :u.id]
+                          [:report_card :card] [:= :qe.card_id :card.id]]
+              :order-by  [[:qe.started_at :desc]]})
+   :xform (map #(update (vec %) 3 codec/base64-encode))})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/tables.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/tables.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e852de40297646d544d755f61af0e41fe1114c35
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/tables.clj
@@ -0,0 +1,81 @@
+(ns metabase-enterprise.audit.pages.tables
+  (:require [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.honeysql-extensions :as hx]
+            [schema.core :as s]))
+
+;; WITH table_executions AS (
+;;     SELECT t.id AS table_id, count(*) AS executions
+;;     FROM query_execution qe
+;;     JOIN report_card card ON qe.card_id = card.id
+;;     JOIN metabase_table t ON card.table_id = t.id
+;;     GROUP BY t.id
+;;     ORDER BY count(*) {{asc-or-desc}}
+;;     LIMIT 10
+;; )
+;;
+;; SELECT tx.table_id, (db.name || ' ' || t.schema || ' ' t.name) AS table_name, tx.executions
+;; FROM table_executions tx
+;; JOIN metabase_table     t ON tx.table_id = t.id
+;; JOIN metabase_database db ON t.db_id = db.id
+;; ORDER BY executions {{asc-or-desc}}
+(defn- query-counts [asc-or-desc]
+  {:metadata [[:table_id   {:display_name "Table ID",   :base_type :type/Integer, :remapped_to   :table_name}]
+              [:table_name {:display_name "Table",      :base_type :type/Title,   :remapped_from :table_id}]
+              [:executions {:display_name "Executions", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with [[:table_executions {:select [[:t.id :table_id]
+                                                   [:%count.* :executions]]
+                                          :from   [[:query_execution :qe]]
+                                          :join   [[:report_card :card]     [:= :qe.card_id :card.id]
+                                                   [:metabase_table :t]     [:= :card.table_id :t.id]]
+                                          :group-by [:t.id]
+                                          :order-by [[:%count.* asc-or-desc]]
+                                          :limit    10}]]
+               :select [:tx.table_id
+                        [(hx/concat :db.name (hx/literal " ") :t.schema (hx/literal " ") :t.name) :table_name]
+                        :tx.executions]
+               :from [[:table_executions :tx]]
+               :join [[:metabase_table :t]     [:= :tx.table_id :t.id]
+                      [:metabase_database :db] [:= :t.db_id :db.id]]
+               :order-by [[:executions asc-or-desc]]})})
+
+(defn ^:internal-query-fn most-queried
+  "Query that returns the top-10 most-queried Tables, in descending order."
+  []
+  (query-counts :desc))
+
+(defn ^:internal-query-fn least-queried
+  "Query that returns the top-10 least-queried Tables (with at least one query execution), in ascending order."
+  []
+  (query-counts :asc))
+
+
+
+(s/defn ^:internal-query-fn table
+  "A table of Tables."
+  ([]
+   (table nil))
+  ([query-string :- (s/maybe s/Str)]
+   {:metadata [[:database_id        {:display_name "Database ID",        :base_type :type/Integer, :remapped_to   :database_name}]
+               [:database_name      {:display_name "Database",           :base_type :type/Text,    :remapped_from :database_id}]
+               [:schema_id          {:display_name "Schema ID",          :base_type :type/Text,   :remapped_to   :schema_name}]
+               [:table_schema       {:display_name "Schema",             :base_type :type/Text,    :remapped_from :schema_id}]
+               [:table_id           {:display_name "Table ID",           :base_type :type/Integer, :remapped_to   :table_name}]
+               [:table_name         {:display_name "Table Name in DB",   :base_type :type/Name,    :remapped_from :table_id}]
+               [:table_display_name {:display_name "Table Display Name", :base_type :type/Text}]]
+    :results (common/reducible-query
+               (->
+                {:select   [[:db.id :database_id]
+                            [:db.name :database_name]
+                            [(hx/concat :db.id (hx/literal ".") :t.schema) :schema_id]
+                            [:t.schema :table_schema]
+                            [:t.id :table_id]
+                            [:t.name :table_name]
+                            [:t.display_name :table_display_name]]
+                 :from     [[:metabase_table :t]]
+                 :join     [[:metabase_database :db] [:= :t.db_id :db.id]]
+                 :order-by [[:%lower.db.name  :asc]
+                            [:%lower.t.schema :asc]
+                            [:%lower.t.name   :asc]]
+                 :where    [:= :t.active true]}
+                (common/add-search-clause query-string :db.name :t.schema :t.name :t.display_name)))}))
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/user_detail.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/user_detail.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c1f4b90db2ea07d9b53136cf87f6e1430af395df
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/user_detail.clj
@@ -0,0 +1,271 @@
+(ns metabase-enterprise.audit.pages.user-detail
+  (:require [honeysql.core :as hsql]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase-enterprise.audit.pages.common
+             [cards :as cards]
+             [dashboards :as dashboards]]
+            [metabase.util
+             [honeysql-extensions :as hx]
+             [schema :as su]
+             [urls :as urls]]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(s/defn ^:internal-query-fn table
+  "Query that probides a single row of information about a given User, similar to the `users/table` query but restricted
+  to a single result.
+  (TODO - in the designs, this is pivoted; should we do that here in Clojure-land?)"
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:name             {:display_name "Name",             :base_type :type/Name}]
+              [:role             {:display_name "Role",             :base_type :type/Text}]
+              [:groups           {:display_name "Groups",           :base_type :type/Text}]
+              [:date_joined      {:display_name "Date Joined",      :base_type :type/DateTime}]
+              [:last_active      {:display_name "Last Active",      :base_type :type/DateTime}]
+              [:signup_method    {:display_name "Signup Method",    :base_type :type/Text}]
+              [:questions_saved  {:display_name "Questions Saved",  :base_type :type/Integer}]
+              [:dashboards_saved {:display_name "Dashboards Saved", :base_type :type/Integer}]
+              [:pulses_saved     {:display_name "Pulses Saved",     :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with   [[:last_query {:select [[:%max.started_at :started_at]]
+                                      :from   [:query_execution]
+                                      :where  [:= :executor_id user-id]}]
+                        [:groups {:select    [[(common/group-concat :pg.name ", ") :groups]]
+                                  :from      [[:permissions_group_membership :pgm]]
+                                  :left-join [[:permissions_group :pg] [:= :pgm.group_id :pg.id]]
+                                  :where     [:= :pgm.user_id user-id]}]
+                        [:questions_saved {:select [[:%count.* :count]]
+                                           :from   [:report_card]
+                                           :where  [:= :creator_id user-id]}]
+                        [:dashboards_saved {:select [[:%count.* :count]]
+                                            :from   [:report_dashboard]
+                                            :where  [:= :creator_id user-id]}]
+                        [:pulses_saved {:select [[:%count.* :count]]
+                                        :from   [:pulse]
+                                        :where  [:= :creator_id user-id]}]
+                        [:users {:select [[(common/user-full-name :u) :name]
+                                          [(hsql/call :case
+                                                      [:= :u.is_superuser true]
+                                                      (hx/literal "Admin")
+                                                      :else
+                                                      (hx/literal "User"))
+                                           :role]
+                                          :id
+                                          :date_joined
+                                          [(hsql/call :case
+                                                      [:= nil :u.sso_source]
+                                                      (hx/literal "Email")
+                                                      :else
+                                                      :u.sso_source)
+                                           :signup_method]
+                                          :last_name]
+                                 :from   [[:core_user :u]]
+                                 :where  [:= :u.id user-id]}]]
+               :select [:u.name
+                        :u.role
+                        :groups.groups
+                        :u.date_joined
+                        [:last_query.started_at :last_active]
+                        :u.signup_method
+                        [:questions_saved.count :questions_saved]
+                        [:dashboards_saved.count :dashboards_saved]
+                        [:pulses_saved.count :pulses_saved]]
+               :from   [[:users :u]
+                        :groups
+                        :last_query
+                        :questions_saved
+                        :dashboards_saved
+                        :pulses_saved]})})
+
+(s/defn ^:internal-query-fn most-viewed-dashboards
+  "Return the 10 most-viewed Dashboards for a given User, in descending order."
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:dashboard_id   {:display_name "Dashboard ID", :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name {:display_name "Dashboard",    :base_type :type/Name,    :remapped_from :dashboard_id}]
+              [:count          {:display_name "Views",        :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select    [[:d.id :dashboard_id]
+                           [:d.name :dashboard_name]
+                           [:%count.* :count]]
+               :from      [[:view_log :vl]]
+               :left-join [[:report_dashboard :d] [:= :vl.model_id :d.id]]
+               :where     [:and
+                           [:= :vl.user_id user-id]
+                           [:= :vl.model (hx/literal "dashboard")]]
+               :group-by  [:d.id]
+               :order-by  [[:%count.* :desc]]
+               :limit     10})})
+
+(s/defn ^:internal-query-fn most-viewed-questions
+  "Return the 10 most-viewed Questions for a given User, in descending order."
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:card_id   {:display_name "Card ID", :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name {:display_name "Query",   :base_type :type/Name,    :remapped_from :card_id}]
+              [:count     {:display_name "Views",   :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:select    [[:d.id :card_id]
+                           [:d.name :card_name]
+                           [:%count.* :count]]
+               :from      [[:view_log :vl]]
+               :left-join [[:report_card :d] [:= :vl.model_id :d.id]]
+               :where     [:and
+                           [:= :vl.user_id user-id]
+                           [:= :vl.model (hx/literal "card")]]
+               :group-by  [:d.id]
+               :order-by  [[:%count.* :desc]]
+               :limit     10})})
+
+(s/defn ^:internal-query-fn query-views
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:viewed_on     {:display_name "Viewed On",      :base_type :type/DateTime}]
+              [:card_id       {:display_name "Card ID"         :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name     {:display_name "Query",          :base_type :type/Text,    :remapped_from :card_id}]
+              [:query_hash    {:display_name "Query Hash",     :base_type :type/Text}]
+              [:type          {:display_name "Type",           :base_type :type/Text}]
+              [:collection_id {:display_name "Collection ID",  :base_type :type/Integer, :remapped_to   :collection}]
+              [:collection    {:display_name "Collection",     :base_type :type/Text,    :remapped_from :collection_id}]
+              [:saved_by_id   {:display_name "Saving User ID", :base_type :type/Integer, :remapped_to   :saved_by}]
+              [:saved_by      {:display_name "Saved By",       :base_type :type/Text,    :remapped_from :saved_by_id}]
+              [:database_id   {:display_name "Database ID",    :base_type :type/Integer, :remapped_to   :source_db}]
+              [:source_db     {:display_name "Source DB",      :base_type :type/Text,    :remapped_from :database_id}]
+              [:table_id      {:display_name "Table ID"        :base_type :type/Integer, :remapped_to   :table}]
+              [:table         {:display_name "Table",          :base_type :type/Text,    :remapped_from :table_id}]]
+   :results (common/reducible-query
+             {:select    [[:qe.started_at :viewed_on]
+                          [:card.id :card_id]
+                          [(common/card-name-or-ad-hoc :card) :card_name]
+                          [:qe.hash :query_hash]
+                          [(common/native-or-gui :qe) :type]
+                          [:collection.id :collection_id]
+                          [:collection.name :collection]
+                          [:u.id :saved_by_id]
+                          [(common/user-full-name :u) :saved_by]
+                          [:db.id :database_id]
+                          [:db.name :source_db]
+                          [:t.id :table_id]
+                          [:t.display_name :table]]
+              :from      [[:query_execution :qe]]
+              :join      [[:metabase_database :db] [:= :qe.database_id :db.id]]
+              :left-join [[:report_card :card]     [:= :qe.card_id :card.id]
+                          :collection              [:= :card.collection_id :collection.id]
+                          [:core_user :u]          [:= :card.creator_id :u.id]
+                          [:metabase_table :t]     [:= :card.table_id :t.id]]
+              :where     [:= :qe.executor_id user-id]
+              :order-by  [[:qe.started_at :desc]]})
+   :xform    (map #(update (vec %) 3 codec/base64-encode))})
+
+(s/defn ^:internal-query-fn dashboard-views
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:timestamp       {:display_name "Viewed on",     :base_type :type/DateTime}]
+              [:dashboard_id    {:display_name "Dashboard ID",  :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name  {:display_name "Dashboard",     :base_type :type/Text,    :remapped_from :dashboard_id}]
+              [:collection_id   {:display_name "Collection ID", :base_type :type/Integer, :remapped_to   :collection_name}]
+              [:collection_name {:display_name "Collection",    :base_type :type/Text,    :remapped_from :collection_id}]]
+   :results (common/reducible-query
+             {:select    [:vl.timestamp
+                          [:dash.id :dashboard_id]
+                          [:dash.name :dashboard_name]
+                          [:coll.id :collection_id]
+                          [:coll.name :collection_name]]
+              :from      [[:view_log :vl]]
+              :where     [:and
+                          [:= :vl.model (hx/literal "dashboard")]
+                          [:= :vl.user_id user-id]]
+              :join      [[:report_dashboard :dash] [:= :vl.model_id :dash.id]]
+              :left-join [[:collection :coll] [:= :dash.collection_id :coll.id]]
+              :order-by  [[:vl.timestamp :desc]]})})
+
+(s/defn ^:internal-query-fn object-views-by-time
+  "Timeseries chart that shows the number of Question or Dashboard views for a User, broken out by `datetime-unit`."
+  [user-id :- su/IntGreaterThanZero, model :- (s/enum "card" "dashboard"), datetime-unit :- common/DateTimeUnitStr]
+  {:metadata [[:date {:display_name "Date",   :base_type (common/datetime-unit-str->base-type datetime-unit)}]
+              [:views {:display_name "Views", :base_type :type/Integer}]]
+   :results (common/reducible-query
+             {:select   [[(common/grouped-datetime datetime-unit :timestamp) :date]
+                         [:%count.* :views]]
+              :from     [:view_log]
+              :where    [:and
+                         [:= :user_id user-id]
+                         [:= :model model]]
+              :group-by [(common/grouped-datetime datetime-unit :timestamp)]
+              :order-by [[(common/grouped-datetime datetime-unit :timestamp) :asc]]})})
+
+(s/defn ^:internal-query-fn created-dashboards
+  ([user-id]
+   (created-dashboards user-id nil))
+  ([user-id :- su/IntGreaterThanZero, query-string :- (s/maybe s/Str)]
+   (dashboards/table query-string [:= :u.id user-id])))
+
+(s/defn ^:internal-query-fn created-questions
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:card_id             {:display_name "Card ID",              :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name           {:display_name "Title",                :base_type :type/Name,    :remapped_from :card_id}]
+              [:collection_id       {:display_name "Collection ID",        :base_type :type/Integer, :remapped_to   :collection_name}]
+              [:collection_name     {:display_name "Collection",           :base_type :type/Text,    :remapped_from :collection_id}]
+              [:created_at          {:display_name "Created At",           :base_type :type/DateTime}]
+              [:database_id         {:display_name "Database ID",          :base_type :type/Integer, :remapped_to   :database_name}]
+              [:database_name       {:display_name "Database",             :base_type :type/Text,    :remapped_from :database_id}]
+              [:table_id            {:display_name "Table ID",             :base_type :type/Integer, :remapped_to   :table_name}]
+              [:table_name          {:display_name "Table",                :base_type :type/Text,    :remapped_from :table_id}]
+              [:avg_running_time_ms {:display_name "Avg. exec. time (ms)", :base_type :type/Number}]
+              [:cache_ttl           {:display_name "Cache TTL",            :base_type :type/Number}]
+              [:public_link         {:display_name "Public Link",          :base_type :type/URL}]
+              [:total_views         {:display_name "Total Views",          :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with      [cards/avg-exec-time
+                           cards/views]
+               :select    [[:card.id :card_id]
+                           [:card.name :card_name]
+                           [:coll.id :collection_id]
+                           [:coll.name :collection_name]
+                           :card.created_at
+                           :card.database_id
+                           [:db.name :database_name]
+                           :card.table_id
+                           [:t.name :table_name]
+                           :avg_exec_time.avg_running_time_ms
+                           :card.cache_ttl
+                           [(hsql/call :case
+                              [:not= :card.public_uuid nil]
+                              (hx/concat (urls/public-card-prefix) :card.public_uuid))
+                            :public_link]
+                           [:card_views.count :total_views]]
+               :from      [[:report_card :card]]
+               :left-join [:avg_exec_time           [:= :card.id :avg_exec_time.card_id]
+                           [:metabase_database :db] [:= :card.database_id :db.id]
+                           [:metabase_table :t]     [:= :card.table_id :t.id]
+                           [:collection :coll]      [:= :card.collection_id :coll.id]
+                           :card_views              [:= :card.id :card_views.card_id]]
+               :where     [:= :card.creator_id user-id]
+               :order-by  [[:%lower.card.name :asc]]})})
+
+(s/defn ^:internal-query-fn downloads
+  "Table of query downloads (i.e., queries whose results are returned as CSV/JSON/XLS) done by this user, ordered by
+  most recent."
+  [user-id :- su/IntGreaterThanZero]
+  {:metadata [[:downloaded_at   {:display_name "Downloaded At",   :base_type :type/DateTime}]
+              [:rows_downloaded {:display_name "Rows Downloaded", :base_type :type/Integer}]
+              [:card_id         {:display_name "Card ID",         :base_type :type/Integer, :remapped_to :card_name}]
+              [:card_name       {:display_name "Query",           :base_type :type/Text,    :remapped_from :card_id}]
+              [:query_type      {:display_name "Query Type",      :base_type :type/Text}]
+              [:database_id     {:display_name "Database ID",     :base_type :type/Integer, :remapped_to :database}]
+              [:database        {:display_name "Database",        :base_type :type/Text,    :remapped_from :database_id}]
+              [:table_id        {:display_name "Table ID",        :base_type :type/Integer, :remapped_to :source_table}]
+              [:source_table    {:display_name "Source Table",    :base_type :type/Text,    :remapped_from :table_id}]]
+   :results  (common/reducible-query
+               {:select    [[:qe.started_at :downloaded_at]
+                            [:qe.result_rows :rows_downloaded]
+                            [:card.id :card_id]
+                            [(common/card-name-or-ad-hoc :card) :card_name]
+                            [(common/native-or-gui :qe) :query_type]
+                            [:db.id :database_id]
+                            [:db.name :database]
+                            [:t.id :table_id]
+                            [:t.name :source_table]]
+                :from      [[:query_execution :qe]]
+                :left-join [[:report_card :card] [:= :card.id :qe.card_id]
+                            [:metabase_database :db] [:= :qe.database_id :db.id]
+                            [:metabase_table :t] [:= :card.table_id :t.id]]
+                :where     [:and
+                            [:= :executor_id user-id]
+                            (common/query-execution-is-download :qe)]
+                :order-by  [[:qe.started_at :desc]]})})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/pages/users.clj b/enterprise/backend/src/metabase_enterprise/audit/pages/users.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c7b18a63741caf6cab358e8b744f2e9320b35e64
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/pages/users.clj
@@ -0,0 +1,293 @@
+(ns metabase-enterprise.audit.pages.users
+  (:require [honeysql.core :as hsql]
+            [metabase-enterprise.audit.pages.common :as common]
+            [metabase.util.honeysql-extensions :as hx]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(defn ^:internal-query-fn ^:deprecated active-users-and-queries-by-day
+  "Query that returns data for a two-series timeseries: the number of DAU (a User is considered active for purposes of
+  this query if they ran at least one query that day), and total number of queries ran. Broken out by day."
+  []
+  {:metadata [[:users   {:display_name "Users",   :base_type :type/Integer}]
+              [:queries {:display_name "Queries", :base_type :type/Integer}]
+              [:day     {:display_name "Date",    :base_type :type/Date}]]
+   :results  (common/reducible-query
+              {:with     [[:user_qe {:select   [:executor_id
+                                                [:%count.* :executions]
+                                                [(hx/cast :date :started_at) :day]]
+                                     :from     [:query_execution]
+                                     :group-by [:executor_id :day]}]]
+               :select   [[:%count.* :users]
+                          [:%sum.executions :queries]
+                          :day]
+               :from     [:user_qe]
+               :group-by [:day]
+               :order-by [[:day :asc]]})})
+
+
+(s/defn ^:internal-query-fn active-and-new-by-time
+  "Two-series timeseries that returns number of active Users (Users who ran at least one query) and number of new Users,
+  broken out by `datetime-unit`."
+  [datetime-unit :- common/DateTimeUnitStr]
+  {:metadata [[:date         {:display_name "Date",         :base_type (common/datetime-unit-str->base-type datetime-unit)}]
+              [:active_users {:display_name "Active Users", :base_type :type/Integer}]
+              [:new_users    {:display_name "New Users",    :base_type :type/Integer}]]
+   ;; this is so nice and easy to implement in a single query with FULL OUTER JOINS but unfortunately only pg supports
+   ;; them(!)
+   :results  (let [active       (common/query
+                                  {:select   [[(common/grouped-datetime datetime-unit :started_at) :date]
+                                              [:%distinct-count.executor_id :count]]
+                                   :from     [:query_execution]
+                                   :group-by [(common/grouped-datetime datetime-unit :started_at)]})
+                   date->active (zipmap (map :date active) (map :count active))
+                   new          (common/query
+                                  {:select   [[(common/grouped-datetime datetime-unit :date_joined) :date]
+                                              [:%count.* :count]]
+                                   :from     [:core_user]
+                                   :group-by [(common/grouped-datetime datetime-unit :date_joined)]})
+                   date->new    (zipmap (map :date new) (map :count new))
+                   all-dates    (sort (keep identity (distinct (concat (keys date->active)
+                                                                       (keys date->new)))))]
+               (for [date all-dates]
+                 {:date         date
+                  :active_users (date->active date 0)
+                  :new_users    (date->new   date 0)}))})
+
+
+(defn ^:internal-query-fn most-active
+  "Query that returns the 10 most active Users (by number of query executions) in descending order."
+  []
+  {:metadata [[:user_id {:display_name "User ID",          :base_type :type/Integer, :remapped_to   :name}]
+              [:name    {:display_name "Name",             :base_type :type/Name,    :remapped_from :user_id}]
+              [:count   {:display_name "Query Executions", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+              {:with      [[:qe_count {:select   [[:%count.* :count]
+                                                  :qe.executor_id]
+                                       :from     [[:query_execution :qe]]
+                                       :where    [:not= nil :qe.executor_id]
+                                       :group-by [:qe.executor_id]
+                                       :order-by [[:%count.* :desc]]
+                                       :limit    10}]]
+               :select    [[:u.id :user_id]
+                           [(common/user-full-name :u) :name]
+                           [(common/zero-if-null :qe_count.count) :count]]
+               :from      [[:core_user :u]]
+               :left-join [:qe_count [:= :qe_count.executor_id :u.id]]
+               :order-by  [[:count :desc]
+                           [:%lower.u.last_name :asc]
+                           [:%lower.u.first_name :asc]]
+               :limit     10})})
+
+
+(defn ^:internal-query-fn most-saves
+  "Query that returns the 10 Users with the most saved objects in descending order."
+  []
+  {:metadata [[:user_id   {:display_name "User ID",       :base_type :type/Integer, :remapped_to :user_name}]
+              [:user_name {:display_name "Name",          :base_type :type/Name,    :remapped_from :user_id}]
+              [:saves     {:display_name "Saved Objects", :base_type :type/Integer}]]
+   :results  (common/reducible-query
+               {:with      [[:card_saves       {:select   [:creator_id
+                                                           [:%count.* :count]]
+                                                :from     [:report_card]
+                                                :group-by [:creator_id]}]
+                            [:dashboard_saves {:select   [:creator_id
+                                                          [:%count.* :count]]
+                                               :from     [:report_dashboard]
+                                               :group-by [:creator_id]}]
+                            [:pulse_saves     {:select   [:creator_id
+                                                          [:%count.* :count]]
+                                               :from     [:pulse]
+                                               :group-by [:creator_id]}]]
+                :select    [[:u.id :user_id]
+                            [(common/user-full-name :u) :user_name]
+                            [(hx/+ (common/zero-if-null :card_saves.count)
+                                   (common/zero-if-null :dashboard_saves.count)
+                                   (common/zero-if-null :pulse_saves.count))
+                             :saves]]
+                :from      [[:core_user :u]]
+                :left-join [:card_saves      [:= :u.id :card_saves.creator_id]
+                            :dashboard_saves [:= :u.id :dashboard_saves.creator_id]
+                            :pulse_saves     [:= :u.id :pulse_saves.creator_id]]
+                :order-by  [[:saves :desc]
+                            [:u.last_name :asc]
+                            [:u.first_name :asc]]
+                :limit     10})})
+
+
+(defn ^:internal-query-fn query-execution-time-per-user
+  "Query that returns the total time spent executing queries, broken out by User, for the top 10 Users."
+  []
+  {:metadata [[:user_id           {:display_name "User ID",                   :base_type :type/Integer, :remapped_to   :name}]
+              [:name              {:display_name "Name",                      :base_type :type/Name,    :remapped_from :user_id}]
+              [:execution_time_ms {:display_name "Total Execution Time (ms)", :base_type :type/Decimal}]]
+   :results  (common/reducible-query
+               {:with      [[:exec_time {:select   [[:%sum.running_time :execution_time_ms]
+                                                    :qe.executor_id]
+                                         :from     [[:query_execution :qe]]
+                                         :where    [:not= nil :qe.executor_id]
+                                         :group-by [:qe.executor_id]
+                                         :order-by [[:%sum.running_time :desc]]
+                                         :limit    10}]]
+                :select    [[:u.id :user_id]
+                            [(common/user-full-name :u) :name]
+                            [(hsql/call :case [:not= :exec_time.execution_time_ms nil] :exec_time.execution_time_ms
+                                        :else 0)
+                             :execution_time_ms]]
+                :from      [[:core_user :u]]
+                :left-join [:exec_time [:= :exec_time.executor_id :u.id]]
+                :order-by  [[:execution_time_ms :desc]
+                            [:%lower.u.last_name :asc]
+                            [:%lower.u.first_name :asc]]
+                :limit     10})})
+
+(s/defn ^:internal-query-fn table
+  ([]
+   (table nil))
+
+  ([query-string :- (s/maybe s/Str)]
+   {:metadata [[:user_id          {:display_name "User ID",          :base_type :type/Integer, :remapped_to :name}]
+               [:name             {:display_name "Name",             :base_type :type/Name,    :remapped_from :user_id}]
+               [:role             {:display_name "Role",             :base_type :type/Text}]
+               [:groups           {:display_name "Groups",           :base_type :type/Text}]
+               [:date_joined      {:display_name "Date Joined",      :base_type :type/DateTime}]
+               [:last_active      {:display_name "Last Active",      :base_type :type/DateTime}]
+               [:signup_method    {:display_name "Signup Method",    :base_type :type/Text}]
+               [:questions_saved  {:display_name "Questions Saved",  :base_type :type/Integer}]
+               [:dashboards_saved {:display_name "Dashboards Saved", :base_type :type/Integer}]
+               [:pulses_saved     {:display_name "Pulses Saved",     :base_type :type/Integer}]]
+    :results  (common/reducible-query
+               (->
+                {:with      [[:last_query {:select   [[:executor_id :id]
+                                                      [:%max.started_at :started_at]]
+                                           :from     [:query_execution]
+                                           :group-by [:executor_id]}]
+                             [:groups {:select    [[:u.id :id]
+                                                   [(common/group-concat :pg.name ", ") :groups]]
+                                       :from      [[:core_user :u]]
+                                       :left-join [[:permissions_group_membership :pgm] [:= :u.id :pgm.user_id]
+                                                   [:permissions_group :pg]             [:= :pgm.group_id :pg.id]]
+                                       :group-by  [:u.id]}]
+                             [:questions_saved {:select    [[:u.id :id]
+                                                            [:%count.* :count]]
+                                                :from      [[:report_card :c]]
+                                                :left-join [[:core_user :u] [:= :u.id :c.creator_id]]
+                                                :group-by  [:u.id]}]
+                             [:dashboards_saved {:select    [[:u.id :id]
+                                                             [:%count.* :count]]
+                                                 :from      [[:report_dashboard :d]]
+                                                 :left-join [[:core_user :u] [:= :u.id :d.creator_id]]
+                                                 :group-by  [:u.id]}]
+                             [:pulses_saved {:select    [[:u.id :id]
+                                                         [:%count.* :count]]
+                                             :from      [[:pulse :p]]
+                                             :left-join [[:core_user :u] [:= :u.id :p.creator_id]]
+                                             :group-by  [:u.id]}]
+                             [:users {:select [[(common/user-full-name :u) :name]
+                                               [(hsql/call :case
+                                                  [:= :u.is_superuser true]
+                                                  (hx/literal "Admin")
+                                                  :else
+                                                  (hx/literal "User"))
+                                                :role]
+                                               :id
+                                               :date_joined
+                                               [(hsql/call :case
+                                                  [:= nil :u.sso_source]
+                                                  (hx/literal "Email")
+                                                  :else
+                                                  :u.sso_source)
+                                                :signup_method]
+                                               :last_name
+                                               :first_name]
+                                      :from   [[:core_user :u]]}]]
+                 :select    [[:u.id :user_id]
+                             :u.name
+                             :u.role
+                             :groups.groups
+                             :u.date_joined
+                             [:last_query.started_at :last_active]
+                             :u.signup_method
+                             [:questions_saved.count :questions_saved]
+                             [:dashboards_saved.count :dashboards_saved]
+                             [:pulses_saved.count :pulses_saved]]
+                 :from      [[:users :u]]
+                 :left-join [:groups           [:= :u.id :groups.id]
+                             :last_query       [:= :u.id :last_query.id]
+                             :questions_saved  [:= :u.id :questions_saved.id]
+                             :dashboards_saved [:= :u.id :dashboards_saved.id]
+                             :pulses_saved     [:= :u.id :pulses_saved.id]]
+                 :order-by  [[:%lower.u.last_name :asc]
+                             [:%lower.u.first_name :asc]]}
+                (common/add-search-clause query-string :u.first_name :u.last_name)))}))
+
+
+(defn ^:internal-query-fn query-views
+  "Return a log of all query executions, including information about the Card associated with the query and the
+  Collection it is in (both, if applicable) and Database/Table referenced by the query."
+  []
+  {:metadata [[:viewed_on     {:display_name "Viewed On",       :base_type :type/DateTime}]
+              [:card_id       {:display_name "Card ID"          :base_type :type/Integer, :remapped_to   :card_name}]
+              [:card_name     {:display_name "Query",           :base_type :type/Text,    :remapped_from :card_id}]
+              [:query_hash    {:display_name "Query Hash",      :base_type :type/Text}]
+              [:type          {:display_name "Type",            :base_type :type/Text}]
+              [:collection_id {:display_name "Collection ID",   :base_type :type/Integer, :remapped_to   :collection}]
+              [:collection    {:display_name "Collection",      :base_type :type/Text,    :remapped_from :collection_id}]
+              [:viewed_by_id  {:display_name "Viewing User ID", :base_type :type/Integer, :remapped_to   :viewed_by}]
+              [:viewed_by     {:display_name "Viewed By",       :base_type :type/Text,    :remapped_from :viewed_by_id}]
+              [:saved_by_id   {:display_name "Saving User ID",  :base_type :type/Integer, :remapped_to   :saved_by}]
+              [:saved_by      {:display_name "Saved By",        :base_type :type/Text,    :remapped_from :saved_by_id}]
+              [:database_id   {:display_name "Database ID",     :base_type :type/Integer, :remapped_to   :source_db}]
+              [:source_db     {:display_name "Source DB",       :base_type :type/Text,    :remapped_from :database_id}]
+              [:table_id      {:display_name "Table ID"         :base_type :type/Integer, :remapped_to   :table}]
+              [:table         {:display_name "Table",           :base_type :type/Text,    :remapped_from :table_id}]]
+   :results (common/reducible-query
+             {:select    [[:qe.started_at :viewed_on]
+                          [:card.id :card_id]
+                          [(common/card-name-or-ad-hoc :card) :card_name]
+                          [:qe.hash :query_hash]
+                          [(common/native-or-gui :qe) :type]
+                          [:collection.id :collection_id]
+                          [:collection.name :collection]
+                          [:viewer.id :viewed_by_id]
+                          [(common/user-full-name :viewer) :viewed_by]
+                          [:creator.id :saved_by_id]
+                          [(common/user-full-name :creator) :saved_by]
+                          [:db.id :database_id]
+                          [:db.name :source_db]
+                          [:t.id :table_id]
+                          [:t.display_name :table]]
+              :from      [[:query_execution :qe]]
+              :join      [[:metabase_database :db] [:= :qe.database_id :db.id]
+                          [:core_user :viewer]     [:= :qe.executor_id :viewer.id]]
+              :left-join [[:report_card :card]     [:= :qe.card_id :card.id]
+                          :collection              [:= :card.collection_id :collection.id]
+                          [:core_user :creator]    [:= :card.creator_id :creator.id]
+                          [:metabase_table :t]     [:= :card.table_id :t.id]]
+              :order-by  [[:qe.started_at :desc]]})
+   :xform (map #(update (vec %) 3 codec/base64-encode))})
+
+(defn ^:internal-query-fn dashboard-views
+  "Return a log of when all Dashboard views, including the Collection the Dashboard belongs to."
+  []
+  {:metadata [[:timestamp       {:display_name "Viewed on",     :base_type :type/DateTime}]
+              [:dashboard_id    {:display_name "Dashboard ID",  :base_type :type/Integer, :remapped_to   :dashboard_name}]
+              [:dashboard_name  {:display_name "Dashboard",     :base_type :type/Text,    :remapped_from :dashboard_id}]
+              [:collection_id   {:display_name "Collection ID", :base_type :type/Integer, :remapped_to   :collection_name}]
+              [:collection_name {:display_name "Collection",    :base_type :type/Text,    :remapped_from :collection_id}]
+              [:user_id         {:display_name "User ID",      :base_type :type/Integer,  :remapped_to   :user_name}]
+              [:user_name       {:display_name "Viewed By",    :base_type :type/Text,     :remapped_from :user_id}]]
+   :results (common/reducible-query
+             {:select    [:vl.timestamp
+                          [:dash.id :dashboard_id]
+                          [:dash.name :dashboard_name]
+                          [:coll.id :collection_id]
+                          [:coll.name :collection_name]
+                          [:u.id :user_id]
+                          [(common/user-full-name :u) :user_name]]
+              :from      [[:view_log :vl]]
+              :where     [:= :vl.model (hx/literal "dashboard")]
+              :join      [[:report_dashboard :dash] [:= :vl.model_id :dash.id]
+                          [:core_user :u]           [:= :vl.user_id :u.id]]
+              :left-join [[:collection :coll] [:= :dash.collection_id :coll.id]]
+              :order-by  [[:vl.timestamp :desc]]})})
diff --git a/enterprise/backend/src/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries.clj b/enterprise/backend/src/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries.clj
new file mode 100644
index 0000000000000000000000000000000000000000..a882a99bbaa86339c4a34996bf4077055fb6fe53
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries.clj
@@ -0,0 +1,165 @@
+(ns metabase-enterprise.audit.query-processor.middleware.handle-audit-queries
+  "Middleware that handles special `internal` type queries. `internal` queries are implementeed directly by Clojure
+  functions, and do not neccesarily need to query a database to provide results; by default, they completely skip
+  the rest of the normal QP pipeline. `internal` queries should look like the following:
+
+    {:type :internal
+     :fn   \"metabase-enterprise.audit.pages.dashboards/table\"
+     :args []} ; optional vector of args to pass to the fn above
+
+  To run an `internal` query, you must have superuser permissions, and the function itself must be tagged as an
+  `:internal-query-fn`. This middleware will automatically resolve the function as appropriate, loading its namespace
+  if needed.
+
+    (defn ^:internal-query-fn table []
+      {:metadata ..., :results ...})
+
+  The function should return a map with two keys, `:metadata` and `:results`, in either the 'legacy' or 'reducible'
+  format:
+
+  LEGACY FORMAT:
+
+  *  `:metadata` is a series of [col-name metadata-map] pairs.
+  *  `:results` is a series of maps.
+
+    {:metadata [[:title {:display_name \"Title\", :base_type :type/Text}]
+                [:count {:display_name \"Count\", :base_type :type/Integer}]]
+     :results  [{:title \"Birds\", :count 2}
+                {:title \"Cans\", :count 2}]}
+
+  REDUCIBLE FORMAT:
+
+  *  `:metadata` is the same as the legacy format.
+  *  `:results` is a function that takes `context` and returns something that can be reduced.
+  *  `:xform` is an optional xform to apply to each result row while reducing the query
+
+    {:metadata ...
+     :results  (fn [context] ...)
+     :xform    ...}"
+  (:require [clojure
+             [data :as data]
+             [string :as str]]
+            [metabase.api.common :as api]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.query-processor
+             [context :as context]
+             [error-type :as error-type]]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
+            [schema.core :as s]))
+
+(def ^:private ResultsMetadata
+  "Schema for the expected format for `:metadata` returned by an internal query function."
+  (su/non-empty
+   [[(s/one su/KeywordOrString "field name")
+     (s/one {:base_type su/FieldType, :display_name su/NonBlankString, s/Keyword s/Any}
+            "field metadata")]]))
+
+(defn- check-results-and-metadata-keys-match
+  "Primarily for dev and debugging purposes. We can probably take this out when shipping the finished product."
+  [results metadata]
+  (let [results-keys  (set (keys (first results)))
+        metadata-keys (set (map (comp keyword first) metadata))]
+    (when (and (seq results-keys)
+               (not= results-keys metadata-keys))
+      (let [[only-in-results only-in-metadata] (data/diff results-keys metadata-keys)]
+        (throw
+         (Exception.
+          (str "results-keys and metadata-keys differ.\n"
+               "results-keys:" results-keys "\n"
+               "metadata-keys:" metadata-keys "\n"
+               "in results, but not metadata:" only-in-results "\n"
+               "in metadata, but not results:" only-in-metadata)))))))
+
+(defn- metadata->cols [metadata]
+  (for [[k v] metadata]
+    (assoc v :name (name k))))
+
+(s/defn ^:private format-results [{:keys [results metadata]} :- {:results  [su/Map]
+                                                                 :metadata ResultsMetadata}]
+  (check-results-and-metadata-keys-match results metadata)
+  {:cols (metadata->cols metadata)
+   :rows (for [row results]
+           (for [[k] metadata]
+             (get row (keyword k))))})
+
+(def InternalQuery
+  "Schema for a valid `internal` type query."
+  {:type                    (s/enum :internal "internal")
+   :fn                      #"^([\w\d-]+\.)*[\w\d-]+/[\w\d-]+$" ; namespace-qualified symbol
+   (s/optional-key :args)   [s/Any]
+   s/Any                    s/Any})
+
+(def ^:dynamic *additional-query-params*
+  "Additional `internal` query params beyond `type`, `fn`, and `args`. These are bound to this dynamic var which is a
+  chance to do something clever outside of the normal function args. For example audit app uses `limit` and `offset`
+  to implement paging for all audit app queries automatically."
+  nil)
+
+(def ^:private resolve-internal-query-fn-lock (Object.))
+
+(defn- resolve-internal-query-fn
+  "Returns the varr for the internal query fn."
+  [qualified-fn-str]
+  (let [[ns-str] (str/split qualified-fn-str #"/")]
+    (or
+     ;; resolve if already available...
+     (locking resolve-internal-query-fn-lock
+       (resolve (symbol qualified-fn-str))
+       ;; if not, load the namespace...
+       (require (symbol ns-str))
+       ;; ...then try resolving again
+       (resolve (symbol qualified-fn-str)))
+     ;; failing that, throw an Exception
+     (throw
+      (Exception.
+       (str (tru "Unable to run internal query function: cannot resolve {0}"
+                 qualified-fn-str)))))))
+
+(defn- reduce-reducible-results [rff context {:keys [metadata results xform], :or {xform identity}}]
+  (let [cols           (metadata->cols metadata)
+        reducible-rows (results context)
+        rff*           (fn [metadata]
+                         (xform (rff metadata)))]
+    (assert (some? cols))
+    (assert (instance? clojure.lang.IReduceInit reducible-rows))
+    (context/reducef rff* context {:cols cols} reducible-rows)))
+
+(defn- reduce-legacy-results [rff context results]
+  (let [{:keys [cols rows]} (format-results results)]
+    (assert (some? cols))
+    (assert (some? rows))
+    (context/reducef rff context {:cols cols} rows)))
+
+(defn- reduce-results [rff context {rows :results, :as results}]
+  ((if (fn? rows)
+     reduce-reducible-results
+     reduce-legacy-results) rff context results))
+
+(s/defn ^:private process-internal-query
+  [{qualified-fn-str :fn, args :args, :as query} :- InternalQuery rff context]
+  ;; Make sure current user is a superuser
+  (api/check-superuser)
+  ;; Make sure audit app is enabled (currently the only use case for internal queries). We can figure out a way to
+  ;; allow non-audit-app queries if and when we add some
+  (when-not (metastore/enable-audit-app?)
+    (throw (ex-info (tru "Audit App queries are not enabled on this instance.")
+                    {:type error-type/invalid-query})))
+  ;;now resolve the query
+  (let [fn-varr (resolve-internal-query-fn qualified-fn-str)]
+    ;; Make sure this is actually allowed to be a internal query fn & has the results metadata we'll need
+    (when-not (:internal-query-fn (meta fn-varr))
+      (throw (Exception. (str (tru "Invalid internal query function: {0} is not marked as an ^:internal-query-fn"
+                                   qualified-fn-str)))))
+    (binding [*additional-query-params* (dissoc query :fn :args)]
+      (let [results (apply @fn-varr args)]
+        (reduce-results rff context results)))))
+
+(defn handle-internal-queries
+  "Middleware that handles `internal` type queries."
+  [qp]
+  (fn [{query-type :type, :as query} xform context]
+    (if (= :internal (keyword query-type))
+      (process-internal-query query xform context)
+      (qp query xform context))))
diff --git a/enterprise/backend/src/metabase_enterprise/core.clj b/enterprise/backend/src/metabase_enterprise/core.clj
new file mode 100644
index 0000000000000000000000000000000000000000..42b0b998ca029e64ff6e81aa4ae6e44bca4cfc61
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/core.clj
@@ -0,0 +1,2 @@
+(ns metabase-enterprise.core
+  "Empty namespace. This is here solely so we can try to require it and see whether or not EE code is on the classpath.")
diff --git a/enterprise/backend/src/metabase_enterprise/enhancements/ee_strategy_impl.clj b/enterprise/backend/src/metabase_enterprise/enhancements/ee_strategy_impl.clj
new file mode 100644
index 0000000000000000000000000000000000000000..7ebec82c1d3d56e7ffc15c5e029087102a57b6ec
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/enhancements/ee_strategy_impl.clj
@@ -0,0 +1,84 @@
+(ns metabase-enterprise.enhancements.ee-strategy-impl
+  "Macro for reifying an object that hands off method invocations to one implementation if EE features are
+  enabled (i.e., if we have a valid token) or to a different implementation if they are not.
+
+  In OO pattern terminology, this is an implementation of the strategy pattern -- the implementation of the interface
+  is determined at runtime."
+  (:require [clojure.string :as str]
+            [metabase.public-settings.metastore :as settings.metastore]
+            [pretty.core :refer [PrettyPrintable]]))
+
+(defn invoke-ee-when-enabled
+  "Impo for `reify-ee-strategy-impl`. Invoke `method` using `ee-impl` if EE features are enabled, otherwise invoke with
+  `oss-impl`."
+  [method ee-impl oss-impl & args]
+  (let [impl (if (settings.metastore/enable-enhancements?)
+               ee-impl
+               oss-impl)]
+    (apply method impl args)))
+
+(defn- resolve-protocol
+  "Resolve a protocol symbol like `LDAPIntegration` to a protocol *map*."
+  [protocol-symbol]
+  (or (let [resolved (resolve protocol-symbol)]
+        (if (class? resolved)
+          (let [symb (symbol (-> (.getCanonicalName ^Class resolved)
+                                 (str/replace  #"(^.+)\.([^\.]+$)" "$1/$2")
+                                 (str/replace #"_" "-")))]
+            ;; this macro only works on Clojure Protocols at this time, because we use the map definition of the
+            ;; protocol to generate the method implementation forms. It *could* work with normal Java interfaces using
+            ;; reflection, but there hasn't been a need for it yet at this point. We can add it if we need it
+            (var-get (or (resolve symb)
+                         (throw (ex-info (format "Could not find protocol %s. `ee-strategy-impl` only works on protocols at this time."
+                                                 symb)
+                                         {:protocol symb})))))
+          (some-> resolved var-get)))
+      (throw (ex-info (format "Could not resolve protocol %s." protocol-symbol)
+                      {:protocol protocol-symbol}))))
+
+(defn- generate-method-impl [ee-impl-symbol oss-impl-symbol protocol-map {method-name :name, arglists :arglists}]
+  (let [arg-counts            (map count arglists)
+        protocol-namespace    (:ns (meta (:var protocol-map)))
+        qualified-method-name (symbol (name (ns-name protocol-namespace))
+                                      (name method-name))]
+    (when-not (distinct? arg-counts)
+      (throw (ex-info "ee-strategy-impl does not work with overloaded methods with the same number of args at this time."
+                      {:method   method-name
+                       :arglists arglists})))
+    (for [arg-count (sort arg-counts)
+          :let      [args (for [n (range (dec arg-count))]
+                            (symbol (str (char (+ (int \a) n)))))]]
+      `(~method-name [~'_ ~@args]
+        (invoke-ee-when-enabled ~qualified-method-name ~ee-impl-symbol ~oss-impl-symbol ~@args)))))
+
+(defn- generate-protocol-impl [ee-impl-symbol oss-impl-symbol protocol-symbol]
+  (let [protocol-map (resolve-protocol protocol-symbol)]
+    (cons
+     (symbol (.getCanonicalName ^Class (:on-interface protocol-map)))
+     (mapcat (partial generate-method-impl ee-impl-symbol oss-impl-symbol protocol-map)
+             (vals (:sigs protocol-map))))))
+
+(defmacro reify-ee-strategy-impl
+  "Reifies a Strategy Pattern object that implements `protocols`. Invocations of protocol methods will be forwarded to
+  `ee-impl` if Enterprise Edition features are enabled (i.e., if we have a valid EE token), otherwise they will be
+  forwarded to `oss-impl`.
+
+    ;; For `MyProtocol` methods: invoke `ee-impl` if EE features are enabled, otherwise invoke `oss-impl`
+    (def my-proxy-impl ee-impl oss-impl
+      MyProtocol)
+
+  At the time of this writing, this only works with first-class Clojure Protocols (as opposed to plain Java
+  interfaces), but should the need arise we can change this."
+  {:style/indent [:defn 2]}
+  [ee-impl oss-impl & protocols]
+  (let [ee-impl-symbol  (gensym "ee-impl-")
+        oss-impl-symbol (gensym "oss-impl-")]
+    `(let [~ee-impl-symbol ~ee-impl
+           ~oss-impl-symbol ~oss-impl]
+       (reify
+         PrettyPrintable
+         (~'pretty [~'_]
+          (list `reify-ee-strategy-impl ~ee-impl-symbol ~oss-impl-symbol))
+
+         ~@(mapcat (partial generate-protocol-impl ee-impl-symbol oss-impl-symbol)
+             protocols)))))
diff --git a/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj b/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj
new file mode 100644
index 0000000000000000000000000000000000000000..d7320835fe3fc6e049bef169f552be485c56fac9
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/enhancements/integrations/ldap.clj
@@ -0,0 +1,89 @@
+(ns metabase-enterprise.enhancements.integrations.ldap
+  "The Enterprise version of the LDAP integration is basically the same but also supports syncing user attributes."
+  (:require [metabase-enterprise.enhancements.ee-strategy-impl :as ee-strategy-impl]
+            [metabase.integrations.common :as integrations.common]
+            [metabase.integrations.ldap
+             [default-implementation :as default-impl]
+             [interface :as i]]
+            [metabase.models
+             [setting :as setting :refer [defsetting]]
+             [user :as user :refer [User]]]
+            [metabase.util :as u]
+            [metabase.util
+             [i18n :refer [deferred-tru]]
+             [schema :as su]]
+            [pretty.core :refer [PrettyPrintable]]
+            [schema.core :as s]
+            [toucan.db :as db])
+  (:import com.unboundid.ldap.sdk.LDAPConnectionPool
+           metabase.integrations.ldap.interface.LDAPIntegration))
+
+(def ^:private EEUserInfo
+  (assoc i/UserInfo :attributes (s/maybe {s/Keyword s/Any})))
+
+(defsetting ldap-sync-user-attributes
+  (deferred-tru "Should we sync user attributes when someone logs in via LDAP?")
+  :type :boolean
+  :default true)
+
+;; TODO - maybe we want to add a csv setting type?
+(defsetting ldap-sync-user-attributes-blacklist
+  (deferred-tru "Comma-separated list of user attributes to skip syncing for LDAP users.")
+  :default "userPassword,dn,distinguishedName"
+  :type    :csv)
+
+(defn- syncable-user-attributes [m]
+  (when (ldap-sync-user-attributes)
+    (apply dissoc m :objectclass (map (comp keyword u/lower-case-en) (ldap-sync-user-attributes-blacklist)))))
+
+(defn- attribute-synced-user
+  [{:keys [attributes email]}]
+  (when-let [user (db/select-one [User :id :last_login :login_attributes] :%lower.email (u/lower-case-en email))]
+    (let [syncable-attributes (syncable-user-attributes attributes)]
+      ;; Update User's `login_attributes` if needed
+      (if (and (not= (:login_attributes user) syncable-attributes)
+               (db/update! User (:id user) :login_attributes syncable-attributes))
+        (db/select-one [User :id :last_login] :id (:id user)) ; Reload updated user
+        user))))
+
+(s/defn ^:private find-user* :- (s/maybe EEUserInfo)
+  [ldap-connection :- LDAPConnectionPool
+   username        :- su/NonBlankString
+   settings        :- i/LDAPSettings]
+  (when-let [result (default-impl/search ldap-connection username settings)]
+    (when-let [user-info (default-impl/ldap-search-result->user-info ldap-connection result settings)]
+      (assoc user-info :attributes (syncable-user-attributes result)))))
+
+(s/defn ^:private fetch-or-create-user!* :- (class User)
+  [{:keys [first-name last-name email groups attributes], :as user-info} :- EEUserInfo
+   {:keys [sync-groups?], :as settings}                                  :- i/LDAPSettings]
+  (let [user (or (attribute-synced-user user-info)
+                 (user/create-new-ldap-auth-user!
+                  {:first_name       first-name
+                   :last_name        last-name
+                   :email            email
+                   :login_attributes attributes}))]
+    (u/prog1 user
+      (when sync-groups?
+        (let [group-ids (default-impl/ldap-groups->mb-group-ids groups settings)]
+          (integrations.common/sync-group-memberships! user group-ids))))))
+
+(def ^:private impl
+  (reify
+    PrettyPrintable
+    (pretty [_]
+      `impl)
+
+    LDAPIntegration
+    (find-user [_ ldap-connection username ldap-settings]
+      (find-user* ldap-connection username ldap-settings))
+
+    (fetch-or-create-user! [_ user-info ldap-settings]
+      (fetch-or-create-user!* user-info ldap-settings))))
+
+(def ee-strategy-impl
+  "Enterprise version of the LDAP integration. Uses our EE strategy pattern adapter: if EE features *are* enabled,
+  forwards method invocations to `impl`; if EE features *are not* enabled, forwards method invocations to the
+  default OSS impl."
+  (ee-strategy-impl/reify-ee-strategy-impl impl default-impl/impl
+    LDAPIntegration))
diff --git a/enterprise/backend/src/metabase_enterprise/enhancements/models/native_query_snippet/permissions.clj b/enterprise/backend/src/metabase_enterprise/enhancements/models/native_query_snippet/permissions.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e781c2915de00e31c0968295df42692658c513e2
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/enhancements/models/native_query_snippet/permissions.clj
@@ -0,0 +1,51 @@
+(ns metabase-enterprise.enhancements.models.native-query-snippet.permissions
+  "EE implementation of NativeQuerySnippet permissions."
+  (:require [metabase-enterprise.enhancements.ee-strategy-impl :as ee-strategy-impl]
+            [metabase.models
+             [interface :as i]
+             [permissions :as perms]]
+            [metabase.models.native-query-snippet.permissions :as snippet.perms]
+            [metabase.util.schema :as su]
+            [pretty.core :refer [PrettyPrintable]]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(s/defn ^:private has-parent-collection-perms?
+  [snippet       :- {:collection_id (s/maybe su/IntGreaterThanZero), s/Keyword s/Any}
+   read-or-write :- (s/enum :read :write)]
+  (i/current-user-has-full-permissions? (perms/perms-objects-set-for-parent-collection "snippets" snippet read-or-write)))
+
+(def ^:private ee-impl*
+  (reify
+    PrettyPrintable
+    (pretty [_]
+      `ee-impl*)
+
+    snippet.perms/PermissionsImpl
+    (can-read?* [_ snippet]
+      (has-parent-collection-perms? snippet :read))
+
+    (can-read?* [_ model id]
+      (has-parent-collection-perms? (db/select-one [model :collection_id] :id id) :read))
+
+    (can-write?* [_ snippet]
+      (has-parent-collection-perms? snippet :write))
+
+    (can-write?* [_ model id]
+      (has-parent-collection-perms? (db/select-one [model :collection_id] :id id) :write))
+
+    (can-create?* [_ model m]
+      (has-parent-collection-perms? m :write))
+
+    (can-update?* [_ snippet changes]
+      (and (has-parent-collection-perms? snippet :write)
+           (or (not (contains? changes :collection_id))
+               (has-parent-collection-perms? changes :write))))))
+
+(def ee-impl
+  "EE implementation of NativeQuerySnippet permissions. Uses Collection permissions instead allowing anyone to view or
+  edit all Snippets. (Only when a valid Enterprise Edition token is present. Otherwise, this forwards method
+  invocations to the default impl)."
+  (ee-strategy-impl/reify-ee-strategy-impl ee-impl* snippet.perms/default-impl snippet.perms/PermissionsImpl))
+
+(snippet.perms/set-impl! ee-impl)
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/error_page.mustache b/enterprise/backend/src/metabase_enterprise/sandbox/api/error_page.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..ed01b64139e2e9382152bce973a6194561184574
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/error_page.mustache
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Metabase</title>
+  </head>
+  <body style="font-family: Helvetica, sans-serif;">
+    <div align="center" style="padding: 100px;">
+      <div style="width:1000px;">
+        <img src="../app/assets/img/disconnect.svg">
+
+        <h2>It looks like we weren't able to log you in.</h2>
+
+        <h2><tt>{{errorMessage}}</tt></h2>
+
+        <p style="margin-top: 20px;">
+          Stuck? Need help? Contact us at <a href="{{mailto}}">support@metabase.com</a>.
+        </p>
+
+        <div align="left" style="border:1px dashed gray; padding: 5px; margin-top:100px;">
+          <h3>Additonal Info</h3>
+
+          <h4>Exception Class</h4>
+          <pre>{{exceptionClass}}</pre>
+
+          <h4>Stacktrace</h4>
+          <pre>{{stacktrace}}</pre>
+
+          <h4>Additional Info</h4>
+          <pre>{{additionalData}}</pre>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/gtap.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/gtap.clj
new file mode 100644
index 0000000000000000000000000000000000000000..6bc4b1afa7efb10aacf5224947cbeaa634e1aa08
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/gtap.clj
@@ -0,0 +1,87 @@
+(ns metabase-enterprise.sandbox.api.gtap
+  "`/api/mt/gtap` endpoints, for CRUD operations and the like on GTAPs (Group Table Access Policies)."
+  (:require [compojure.core :refer [DELETE GET POST PUT]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.api.common :as api]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.util :as u]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(api/defendpoint GET "/"
+  "Fetch a list of all the GTAPs currently in use."
+  []
+  ;; TODO - do we need to hydrate anything here?
+  (db/select GroupTableAccessPolicy))
+
+(api/defendpoint GET "/:id"
+  "Fetch GTAP by `id`"
+  [id]
+  (api/check-404 (GroupTableAccessPolicy id)))
+
+;; TODO - not sure what other endpoints we might need, e.g. for fetching the list above but for a given group or Table
+
+#_(def ^:private AttributeRemappings
+  (su/with-api-error-message (s/maybe {su/NonBlankString su/NonBlankString})
+    "value must be a valid attribute remappings map (attribute name -> remapped name)"))
+
+(api/defendpoint POST "/"
+  "Create a new GTAP."
+  [:as {{:keys [table_id card_id group_id attribute_remappings]} :body}]
+  {table_id             su/IntGreaterThanZero
+   card_id              (s/maybe su/IntGreaterThanZero)
+   group_id             su/IntGreaterThanZero
+   #_attribute_remappings #_AttributeRemappings} ; TODO -  fix me
+  (db/insert! GroupTableAccessPolicy
+    {:table_id             table_id
+     :card_id              card_id
+     :group_id             group_id
+     :attribute_remappings attribute_remappings}))
+
+(api/defendpoint PUT "/:id"
+  "Update a GTAP entry. The only things you're allowed to update for a GTAP are the Card being used (`card_id`) or the
+  paramter mappings; changing `table_id` or `group_id` would effectively be deleting this entry and creating a new
+  one. If that's what you want to do, do so explicity with appropriate calls to the `DELETE` and `POST` endpoints."
+  [id :as {{:keys [card_id attribute_remappings], :as body} :body}]
+  {card_id              (s/maybe su/IntGreaterThanZero)
+   #_attribute_remappings #_AttributeRemappings} ; TODO -  fix me
+  (api/check-404 (GroupTableAccessPolicy id))
+  ;; Only update `card_id` and/or `attribute_remappings` if the values are present in the body of the request.
+  ;; This allows existing values to be "cleared" by being set to nil
+  (when (some #(contains? body %) [:card_id :attribute_remappings])
+    (db/update! GroupTableAccessPolicy id
+      (u/select-keys-when body
+        :present #{:card_id :attribute_remappings})))
+  (GroupTableAccessPolicy id))
+
+(api/defendpoint DELETE "/:id"
+  "Delete a GTAP entry."
+  [id]
+  (api/check-404 (GroupTableAccessPolicy id))
+  (db/delete! GroupTableAccessPolicy :id id)
+  api/generic-204-no-content)
+
+
+(defn- +check-sandboxes-enabled
+  "Wrap the Ring handler to make sure sandboxes are enabled before allowing access to the API endpoints."
+  [handler]
+  (fn [request respond raise]
+    (if-not (metastore/enable-sandboxes?)
+      (raise (ex-info (str (tru "Error: sandboxing is not enabled for this instance.")
+                           " "
+                           (tru "Please check you have set a valid Entrprise token and try again."))
+               {:status-code 403}))
+      (handler request respond raise))))
+
+;; All endpoints in this namespace require superuser perms to view
+;;
+;; TODO - does it make sense to have this middleware
+;; here? Or should we just wrap `routes` in the `metabase-enterprise.sandbox.api.routes/routes` table like we do for everything else?
+;;
+;; TODO - defining the `check-superuser` check *here* means the API documentation function won't pick up on the "this
+;; requires a superuser" stuff since it parses the `defendpoint` body to look for a call to `check-superuser`. I
+;; suppose this doesn't matter (much) since this is an enterprise endpoint and won't go in the dox anyway.
+(api/define-routes api/+check-superuser +check-sandboxes-enabled)
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj
new file mode 100644
index 0000000000000000000000000000000000000000..1e59f2e99da8ca5bd138aa1d041ae32d0eb16778
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj
@@ -0,0 +1,23 @@
+(ns metabase-enterprise.sandbox.api.routes
+  "Multi-tenant API routes."
+  (:require [compojure.core :as compojure]
+            [metabase-enterprise.sandbox.api
+             [gtap :as gtap]
+             [table :as table]
+             [user :as user]]
+            [metabase.middleware.auth :as middleware.auth]))
+
+;; this is copied from `metabase.api.routes` because if we require that above we will destroy startup times for `lein
+;; ring server`
+(def ^:private +auth
+  "Wrap `routes` so they may only be accessed with proper authentiaction credentials."
+  middleware.auth/enforce-authentication)
+
+(compojure/defroutes ^{:doc "Ring routes for mt API endpoints."} routes
+  (compojure/context
+   "/mt"
+   []
+   (compojure/routes
+    (compojure/context "/gtap" [] (+auth gtap/routes))
+    (compojure/context "/user" [] (+auth user/routes))))
+  (compojure/context "/table" [] (+auth table/routes)))
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/table.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/table.clj
new file mode 100644
index 0000000000000000000000000000000000000000..584d510fcf7005ff1d3bc82438c99bacd7b741a7
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/table.clj
@@ -0,0 +1,69 @@
+(ns metabase-enterprise.sandbox.api.table
+  (:require [compojure.core :refer [GET]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.api
+             [common :as api]
+             [table :as table-api]]
+            [metabase.mbql.util :as mbql.u]
+            [metabase.models
+             [card :refer [Card]]
+             [permissions :as perms]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]
+             [table :as table :refer [Table]]]
+            [metabase.util :as u]
+            [metabase.util.schema :as su]
+            [schema.core :as s]
+            [toucan
+             [db :as db]
+             [models :as models]]))
+
+(s/defn ^:private find-gtap-question :- (s/maybe (type Card))
+  "Find the associated GTAP question (if there is one) for the given `table-or-table-id` and
+  `user-or-user-id`. Returns nil if no question was found."
+  [table-or-table-id user-or-user-id]
+  (some->> (db/query {:select [:c.id :c.dataset_query]
+                      :from [[GroupTableAccessPolicy :gtap]]
+                      :join [[PermissionsGroupMembership :pgm] [:= :gtap.group_id :pgm.group_id ]
+                             [Card :c] [:= :c.id :gtap.card_id]]
+                      :where [:and
+                              [:= :gtap.table_id (u/get-id table-or-table-id)]
+                              [:= :pgm.user_id (u/get-id user-or-user-id)]]})
+           first
+           (models/do-post-select Card)))
+
+(s/defn ^:private only-segmented-perms? :- s/Bool
+  "Returns true if the user has only segemented and not full table permissions. If the user has full table permissions
+  we wouldn't want to apply this segment filtering."
+  [table :- (type Table)]
+  (and
+   (not (perms/set-has-full-permissions? @api/*current-user-permissions-set*
+          (perms/table-query-path table)))
+   (perms/set-has-full-permissions? @api/*current-user-permissions-set*
+     (perms/table-segmented-query-path table))))
+
+(s/defn ^:private query->fields-ids :- (s/maybe [s/Int])
+  [{{{:keys [fields]} :query} :dataset_query}]
+  (mbql.u/match fields [:field-id id] id))
+
+(defn- maybe-filter-fields [table query-metadata-response]
+  ;; If we have segmented permissions and the associated GTAP limits the fields returned, we need make sure the
+  ;; query_metadata endpoint also excludes any fields the GTAP query would exclude
+  (if-let [gtap-field-ids (and (only-segmented-perms? table)
+                               (seq (query->fields-ids (find-gtap-question table api/*current-user-id*))))]
+    (update query-metadata-response :fields #(filter (comp (set gtap-field-ids) u/get-id) %))
+    query-metadata-response))
+
+(api/defendpoint GET "/:id/query_metadata"
+  "This endpoint essentially acts as a wrapper for the CE version of this route. When a user has segmented
+  permissions that only gives them access to a subset of columns for a given table, those inaccessable columns should
+  also be excluded from what is show in the query builder. When the user has full permissions (or no permissions) this
+  route doesn't add/change anything from the CE version. See the docs on the CE version of the endpoint for more
+  information."
+  [id include_sensitive_fields include_hidden_fields]
+  {include_sensitive_fields (s/maybe su/BooleanString)
+   include_hidden_fields    (s/maybe su/BooleanString)}
+  ;; Permissions checking for table is done in `fetch-query-metadata`
+  (let [table (Table id)]
+    (maybe-filter-fields table (table-api/fetch-query-metadata table include_sensitive_fields include_hidden_fields))))
+
+(api/define-routes)
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/user.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/user.clj
new file mode 100644
index 0000000000000000000000000000000000000000..8ee41c3ac417f78eaf9e03d1efd6de59bd6604b3
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/user.clj
@@ -0,0 +1,37 @@
+(ns metabase-enterprise.sandbox.api.user
+  "Endpoint(s)for setting user attributes."
+  (:require [clojure.set :as set]
+            [compojure.core :refer [GET PUT]]
+            [metabase.api.common :as api]
+            [metabase.models.user :refer [User]]
+            [metabase.util.schema :as su]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(def ^:private UserAttributes
+  (su/with-api-error-message (s/maybe {su/NonBlankString s/Any})
+    "value must be a valid user attributes map (name -> value)"))
+
+;; TODO - not sure we need this endpoint now that we're just letting you edit from the regular `PUT /api/user/:id
+;; endpoint
+(api/defendpoint PUT "/:id/attributes"
+  "Update the `login_attributes` for a User."
+  [id :as {{:keys [login_attributes]} :body}]
+  {login_attributes UserAttributes}
+  (api/check-404 (User id))
+  (db/update! User id :login_attributes login_attributes))
+
+(api/defendpoint GET "/attributes"
+  "Fetch a list of possible keys for User `login_attributes`. This just looks at keys that have already been set for
+  existing Users and returns those. "
+  []
+  (->>
+   ;; look at the `login_attributes` for the first 1000 users that have them set. Then make a set of the keys
+   (for [login-attributes (db/select-field :login_attributes User :login_attributes [:not= nil] {:limit 1000})
+         :when (seq login-attributes)]
+     (set (keys login-attributes)))
+   ;; combine all the sets of attribute keys into a single set
+   (reduce set/union)))
+
+
+(api/define-routes api/+check-superuser)
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/util.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/util.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3802cf4394cd482eb0cbdd08ba5e09981f0ed499
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/util.clj
@@ -0,0 +1,18 @@
+(ns metabase-enterprise.sandbox.api.util
+  "Enterprise specific API utility functions"
+  (:require [metabase.api.common :refer [*current-user-permissions-set* *is-superuser?*]]
+            [metabase.models.permissions :as perms]
+            [metabase.util.i18n :refer [tru]]))
+
+(defn segmented-user?
+  "Returns true if the currently logged in user has segmented permissions"
+  []
+  (boolean
+   (when-not *is-superuser?*
+     (if-let [current-user-perms @*current-user-permissions-set*]
+       (boolean (some #(re-matches perms/segmented-perm-regex %) current-user-perms))
+       ;; If the current permissions are nil, then we would return false which could give a potentially segmented user
+       ;; access they shouldn't have. If we don't have permissions, we can't determine whether they are segmented, so
+       ;; throw.
+       (throw (ex-info (str (tru "No permissions found for current user"))
+                {:status-code 403}))))))
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj b/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj
new file mode 100644
index 0000000000000000000000000000000000000000..954c31e126ad3529b5c17ccd705aa70b3b0832bf
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj
@@ -0,0 +1,38 @@
+(ns metabase-enterprise.sandbox.models.group-table-access-policy
+  "Model definition for Group Table Access Policy, aka GTAP. A GTAP is useed to control access to a certain Table for a
+  certain PermissionsGroup. Whenever a member of that group attempts to query the Table in question, a Saved Question
+  specified by the GTAP is instead used as the source of the query."
+  (:require [clojure.walk :as walk]
+            [medley.core :as m]
+            [metabase.mbql.normalize :as normalize]
+            [metabase.models.interface :as i]
+            [metabase.util :as u]
+            [toucan.models :as models]))
+
+(models/defmodel GroupTableAccessPolicy :group_table_access_policy)
+
+(defn- normalize-attribute-remapping-targets [attribute-remappings]
+  (m/map-vals
+   (fn [target]
+     (if (map? target)
+       (normalize/normalize-tokens (walk/keywordize-keys target) [:parameters :metabase.mbql.normalize/sequence])
+       (normalize/normalize-tokens target :ignore-path)))
+   attribute-remappings))
+
+;; for GTAPs
+(models/add-type! ::attribute-remappings
+  :in  (comp i/json-in normalize-attribute-remapping-targets)
+  :out (comp normalize-attribute-remapping-targets i/json-out-without-keywordization))
+
+(u/strict-extend (class GroupTableAccessPolicy)
+  models/IModel
+  (merge
+   models/IModelDefaults
+   {:types (constantly {:attribute_remappings ::attribute-remappings})})
+
+  ;; only admins can work with GTAPs
+  i/IObjectPermissions
+  (merge
+   i/IObjectPermissionsDefaults
+   {:can-read?  i/superuser?
+    :can-write? i/superuser?}))
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/column_level_perms_check.clj b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/column_level_perms_check.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c1c61f50cea7ce5ef44c151fcdc409f4e26ec071
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/column_level_perms_check.clj
@@ -0,0 +1,24 @@
+(ns metabase-enterprise.sandbox.query-processor.middleware.column-level-perms-check
+  (:require [clojure.tools.logging :as log]
+            [medley.core :as m]
+            [metabase.api.common :refer [*current-user-id*]]
+            [metabase.mbql.util :as mbql.u]
+            [metabase.util.i18n :refer [trs tru]]))
+
+(defn- maybe-apply-column-level-perms-check* [{{{source-query-fields :fields} :source-query} :query, :as query}]
+  (let [restricted-field-ids (and (or (:gtap-perms query)
+                                      (get-in query [:query :gtap-perms]))
+                                  (set (mbql.u/match source-query-fields [:field-id id] id)))]
+    (when (seq restricted-field-ids)
+      (let [fields-ids-in-query (set (mbql.u/match (m/dissoc-in query [:query :source-query]) [:field-id id] id))]
+        (when-not (every? restricted-field-ids fields-ids-in-query)
+          (log/warn (trs "User ''{0}'' attempted to access an inaccessible field. Accessible fields {1}, fields in query {2}"
+                         *current-user-id* (pr-str restricted-field-ids) (pr-str fields-ids-in-query)))
+          (throw (ex-info (str (tru "User not able to query field")) {:status 403})))))))
+
+(defn maybe-apply-column-level-perms-check
+  "Check column-level permissions if applicable."
+  [qp]
+  (fn [query rff context]
+    (maybe-apply-column-level-perms-check* query)
+    (qp query rff context)))
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj
new file mode 100644
index 0000000000000000000000000000000000000000..674eb4e95ed9e8202d99d9c139d643920f3e956f
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions.clj
@@ -0,0 +1,292 @@
+(ns metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions
+  (:require [clojure.tools.logging :as log]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.api.common :as api :refer [*current-user* *current-user-id* *current-user-permissions-set*]]
+            [metabase.mbql
+             [schema :as mbql.s]
+             [util :as mbql.u]]
+            [metabase.models
+             [card :refer [Card]]
+             [field :refer [Field]]
+             [permissions :as perms]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]
+             [table :refer [Table]]]
+            [metabase.models.query.permissions :as query-perms]
+            [metabase.query-processor
+             [error-type :as qp.error-type]
+             [store :as qp.store]]
+            [metabase.query-processor.middleware
+             [add-source-metadata :as add-source-metadata]
+             [annotate :as annotate]
+             [fetch-source-query :as fetch-source-query]]
+            [metabase.util :as u]
+            [metabase.util
+             [i18n :refer [tru]]
+             [schema :as su]]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                  query->gtap                                                   |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- all-table-ids [m]
+  (set
+   (reduce
+    concat
+    (mbql.u/match m
+      (_ :guard (every-pred map? :source-table (complement :gtap?)))
+      (let [recursive-ids (all-table-ids (dissoc &match :source-table))]
+        (cons (:source-table &match) recursive-ids))))))
+
+(defn- query->all-table-ids [query]
+  (let [ids (all-table-ids query)]
+    (when (seq ids)
+      (qp.store/fetch-and-store-tables! ids)
+      (set ids))))
+
+(defn- table-should-have-segmented-permissions?
+  "Determine whether we should apply segmented permissions for `table-or-table-id`."
+  [table-id]
+  (let [table (assoc (qp.store/table table-id) :db_id (u/get-id (qp.store/database)))]
+    (and
+     ;; User does not have full data access
+     (not (perms/set-has-full-permissions? @*current-user-permissions-set* (perms/table-query-path table)))
+     ;; User does have segmented access
+     (perms/set-has-full-permissions? @*current-user-permissions-set* (perms/table-segmented-query-path table)))))
+
+(defn- assert-one-gtap-per-table
+  "Make sure all referenced Tables have at most one GTAP."
+  [gtaps]
+  (doseq [[table-id gtaps] (group-by :table_id gtaps)
+          :when            (> (count gtaps) 1)]
+    (throw (ex-info (tru "Found more than one group table access policy for user ''{0}''"
+                         (:email @*current-user*))
+                    {:type      qp.error-type/client
+                     :table-id  table-id
+                     :gtaps     gtaps
+                     :user      *current-user-id*
+                     :group-ids (map :group_id gtaps)}))))
+
+(defn- tables->gtaps [table-ids]
+  (qp.store/cached [*current-user-id* table-ids]
+    (let [group-ids (qp.store/cached *current-user-id*
+                      (db/select-field :group_id PermissionsGroupMembership :user_id *current-user-id*))
+          gtaps     (when (seq group-ids)
+                      (db/select GroupTableAccessPolicy
+                        :group_id [:in group-ids]
+                        :table_id [:in table-ids]))]
+      (when (seq gtaps)
+        (assert-one-gtap-per-table gtaps)
+        gtaps))))
+
+(defn- query->table-id->gtap [query]
+  {:pre [(some? *current-user-id*)]}
+  (when-let [gtaps (some->> (query->all-table-ids query)
+                            ((comp seq filter) table-should-have-segmented-permissions?)
+                            tables->gtaps)]
+    (u/key-by :table_id gtaps)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                Applying a GTAP                                                 |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(s/defn ^:private target-field->base-type :- (s/maybe su/FieldType)
+  "If the `:target` of a parameter contains a `:field-id` clause, return the base type corresponding to the Field it
+  references. Otherwise returns `nil`."
+  [[_ target-field-clause]]
+  (when-let [field-id (u/ignore-exceptions (mbql.u/field-clause->id-or-literal target-field-clause))]
+    (when (integer? field-id)
+      ;; TODO -- we should be using the QP store for this. But when trying to change this I ran into "QP Store is not
+      ;; initialized" errors. We should figure out why that's the case and then fix this
+      (db/select-one-field :base_type Field :id field-id))))
+
+(defn- attr-value->param-value
+  "Take an `attr-value` with a desired `target-type` and coerce to that type if need be. If not type is given or it's
+  already correct, return the original `attr-value`"
+  [target-type attr-value]
+  (let [attr-string? (string? attr-value)]
+    (cond
+      ;; If the attr-value is a string and the target type is integer, parse it as a long
+      (and attr-string? (isa? target-type :type/Integer))
+      (Long/parseLong attr-value)
+      ;; If the attr-value is a string and the target type is float, parse it as a double
+      (and attr-string? (isa? target-type :type/Float))
+      (Double/parseDouble attr-value)
+      ;; No need to parse it if the type isn't numeric or if it's already a number
+      :else
+      attr-value)))
+
+(defn- attr-remapping->parameter [login-attributes [attr-name target]]
+  (let [attr-value      (get login-attributes attr-name ::not-found)
+        field-base-type (target-field->base-type target)]
+    (when (= attr-value ::not-found)
+      (throw (ex-info (tru "Query requires user attribute `{0}`" (name attr-name))
+                      {:type qp.error-type/missing-required-parameter})))
+    {:type   :category
+     :target target
+     :value  (attr-value->param-value field-base-type attr-value)}))
+
+(defn- gtap->parameters [{attribute-remappings :attribute_remappings}]
+  (mapv (partial attr-remapping->parameter (:login_attributes @*current-user*))
+        attribute-remappings))
+
+(s/defn ^:private preprocess-source-query :- mbql.s/SourceQuery
+  [source-query :- mbql.s/SourceQuery]
+  (let [query        {:database (:id (qp.store/database))
+                      :type     :query
+                      :query    source-query}
+        preprocessed (binding [api/*current-user-id* nil]
+                       ((resolve 'metabase.query-processor/query->preprocessed) query))]
+    (select-keys (:query preprocessed) [:source-query :source-metadata])))
+
+(s/defn ^:private card-gtap->source
+  [{card-id :card_id, :as gtap}]
+  (update-in (fetch-source-query/card-id->source-query-and-metadata card-id)
+             [:source-query :parameters]
+             concat
+             (gtap->parameters gtap)))
+
+(defn- table-gtap->source [{table-id :table_id, :as gtap}]
+  {:source-query {:source-table table-id, :parameters (gtap->parameters gtap)}})
+
+;; If a GTAP source query doesn't have results metadata for whatever reason, we can infer the results metadata if we
+;; assume the results will match the shape of the Table we're GTAPping; figure out what the results metadata for that
+;; Table would be and return that. In practice, this is hopefully a safe assumption to make, but we never explictly
+;; enforce a constraint that a GTAP must return the exact same columns as the Table it replaces. (We probably SHOULD
+;; enforce this constraint.)
+
+(s/defn ^:private source-metadata-for-table :- [mbql.s/SourceQueryMetadata]
+  "Determine the source metadata that would normally come back for a Table with `table-id`."
+  [table-id :- su/IntGreaterThanZero]
+  (log/tracef "GTAP source query has no results Metadata. Inferring metdata from Table %d %s.%s"
+              table-id
+              (pr-str (:schema (qp.store/table table-id)))
+              (pr-str (:name (qp.store/table table-id))))
+  (let [cols (add-source-metadata/mbql-source-query->metadata {:source-table table-id})]
+    (u/prog1 (for [col cols]
+               (select-keys col [:name :base_type :display_name :special_type]))
+      (log/tracef "Inferred source query metadata:\n%s" (u/pprint-to-str 'magenta <>)))))
+
+(s/defn ^:private gtap->source :- {:source-query                     s/Any
+                                   (s/optional-key :source-metadata) [mbql.s/SourceQueryMetadata]
+                                   s/Keyword                         s/Any}
+  "Get the source query associated with a `gtap`."
+  [{card-id :card_id, table-id :table_id, :as gtap} infer-source-metadata?]
+  {:pre [gtap]}
+  (let [source-query (preprocess-source-query ((if card-id card-gtap->source table-gtap->source) gtap))]
+    (cond-> source-query
+      (and infer-source-metadata? (empty? (:source-metadata source-query)))
+      (assoc :source-metadata (source-metadata-for-table table-id)))))
+
+(s/defn ^:private gtap->perms-set :- #{perms/ObjectPath}
+  "Calculate the set of permissions needed to run the query associated with a GTAP; this set of permissions is excluded
+  during the normal QP perms check.
+
+  Background: when applying GTAPs, we don't want the QP perms check middleware to throw an Exception if the Current
+  User doesn't have permissions to run the underlying GTAP query, which will likely be greater than what they actually
+  have. (For example, a User might have segmented query perms for Table 15, which is why we're applying a GTAP in the
+  first place; the actual perms required to normally run the underlying GTAP query is more likely something like
+  *full* query perms for Table 15.) The QP perms check middleware subtracts this set from the set of required
+  permissions, allowing the user to run their GTAPped query."
+  [{card-id :card_id, table-id :table_id}]
+  (if card-id
+    (qp.store/cached card-id
+      (query-perms/perms-set (db/select-one-field :dataset_query Card :id card-id), :throw-exceptions? true))
+    #{(perms/table-query-path (Table table-id))}))
+
+(defn- gtaps->perms-set [gtaps]
+  (set (mapcat gtap->perms-set gtaps)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                   Middleware                                                   |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;;; ------------------------------------------ apply-row-level-permissions -------------------------------------------
+
+(defn- apply-gtap
+  "Apply a GTAP to map m (e.g. a Join or inner query), replacing its `:source-table`/`:source-query` with the GTAP
+  `:source-query`."
+  [m gtap]
+  ;; Only infer source query metadata for JOINS that use `:fields :all`. That's the only situation in which we
+  ;; absolutely *need* to infer source query metadata (we need to know the columns returned by the source query so we
+  ;; can generate the join against ALL fields). It's better not to infer the source metadata if we don't NEED to,
+  ;; because we might be inferring the wrong thing. See comments above -- in practice a GTAP should have the same
+  ;; columns as the Table it replaces, but this constraint is not enforced anywhere. If we infer metadata and the GTAP
+  ;; turns out *not* to match exactly, the query could break. So only infer it in cases where the query would
+  ;; definitely break otherwise.
+  (let [infer-source-metadata? (= (:fields m) :all)]
+    (u/prog1 (merge
+              (dissoc m :source-table :source-query)
+              (gtap->source gtap infer-source-metadata?))
+      (log/tracef "Applied GTAP: replaced\n%swith\n%s"
+                  (u/pprint-to-str 'yellow m)
+                  (u/pprint-to-str 'green <>)))))
+
+(defn- apply-gtaps
+  "Replace `:source-table` entries that refer to Tables for which we have applicable GTAPs with `:source-query` entries
+  from their GTAPs."
+  [m table-id->gtap]
+  ;; replace maps that have `:source-table` key and a matching entry in `table-id->gtap`, but do not have `:gtap?` key
+  (mbql.u/replace m
+    (_ :guard (every-pred map? (complement :gtap?) :source-table #(get table-id->gtap (:source-table %))))
+    (let [updated             (apply-gtap &match (get table-id->gtap (:source-table &match)))
+          ;; now recursively apply gtaps anywhere else they might exist at this level, e.g. `:joins`
+          recursively-updated (merge
+                               (select-keys updated [:source-table :source-query])
+                               (apply-gtaps (dissoc updated :source-table :source-query) table-id->gtap))]
+      ;; add a `:gtap?` key next to every `:source-table` key so when we do a second pass after adding JOINs they
+      ;; don't get processed again
+      (mbql.u/replace recursively-updated
+        (_ :guard (every-pred map? :source-table))
+        (assoc &match :gtap? true)))))
+
+(defn- id->col-info [query field-id]
+  (when field-id
+    (annotate/col-info-for-field-clause (:query query) [:field-id field-id])))
+
+(defn- update-col-metadata [query field-name->id-delay {:keys [id source], field-ref :field_ref, field-name :name, :as col}]
+  (let [id (or id (when (and (= (first field-ref) :field-literal)
+                             field-name)
+                    (get @field-name->id-delay field-name)))]
+    (merge
+     col
+     (id->col-info query id)
+     (when (= source :native)
+       {:source :fields}))))
+
+(defn- merge-metadata
+  "Merge column metadata from the source Table into the current results `metadata`. This way the final results metadata
+  coming back matches what we'd get if the query was not running with a GTAP."
+  [query metadata]
+  (let [source-table-id      (mbql.u/query->source-table-id query)
+        field-name->id-delay (delay
+                               (u/prog1 (when source-table-id
+                                          (db/select-field->id :name Field :table_id source-table-id))
+                                 (qp.store/fetch-and-store-fields! (vals <>))))]
+    (update metadata :cols (fn [cols]
+                             (mapv (partial update-col-metadata query field-name->id-delay) cols)))))
+
+(defn- gtapped-query
+  "Apply GTAPs to `query` and return the updated version of `query`."
+  [query table-id->gtap]
+  (-> query
+      (apply-gtaps table-id->gtap)
+      (update :gtap-perms (fn [perms]
+                            (into (set perms) (gtaps->perms-set (vals table-id->gtap)))))))
+
+(defn apply-row-level-permissions
+  "Does the work of swapping the given table the user was querying against with a nested subquery that restricts the
+  rows returned. Will return the original query if there are no segmented permissions found."
+  [qp]
+  (fn [query rff context]
+    (if-let [table-id->gtap (when *current-user-id*
+                              (query->table-id->gtap query))]
+      (qp
+       (gtapped-query query table-id->gtap)
+       (fn [metadata]
+         (rff (merge-metadata query metadata)))
+       context)
+      (qp query rff context))))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/cmd.clj b/enterprise/backend/src/metabase_enterprise/serialization/cmd.clj
new file mode 100644
index 0000000000000000000000000000000000000000..d3ff720fe58337b7d16537683d6c3b1e0b1f171b
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/cmd.clj
@@ -0,0 +1,94 @@
+(ns metabase-enterprise.serialization.cmd
+  (:refer-clojure :exclude [load])
+  (:require [clojure.tools.logging :as log]
+            [metabase
+             [db :as mdb]
+             [util :as u]]
+            [metabase-enterprise.serialization
+             [dump :as dump]
+             [load :as load]]
+            [metabase.models
+             [card :refer [Card]]
+             [collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
+             [database :refer [Database]]
+             [field :as field :refer [Field]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [segment :refer [Segment]]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.util
+             [i18n :refer [deferred-trs trs]]
+             [schema :as su]]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(def ^:private Mode
+  (su/with-api-error-message (s/enum :skip :update)
+    (deferred-trs "invalid --mode value")))
+
+(def ^:private OnError
+  (su/with-api-error-message (s/enum :continue :abort)
+    (deferred-trs "invalid --on-error value")))
+
+(def ^:private Context
+  (su/with-api-error-message
+    {(s/optional-key :on-error) OnError
+     (s/optional-key :mode)     Mode}
+    (deferred-trs "invalid context seed value")))
+
+(s/defn load
+  "Load serialized metabase instance as created by `dump` command from directory `path`."
+  [path context :- Context]
+  (mdb/setup-db!)
+  (when-not (load/compatible? path)
+    (log/warn (trs "Dump was produced using a different version of Metabase. Things may break!")))
+  (let [context (merge {:mode     :skip
+                        :on-error :continue}
+                       context)]
+    (try
+      (do
+        (load/load (str path "/users") context)
+        (load/load (str path "/databases") context)
+        (load/load (str path "/collections") context)
+        (load/load-settings path context)
+        (load/load-dependencies path context))
+      (catch Throwable e
+        (log/error (trs "Error loading dump: {0}" (.getMessage e)))))))
+
+(defn- select-entities-in-collections
+  [model collections]
+  (db/select model {:where [:or [:= :collection_id nil]
+                                (if (not-empty collections)
+                                  [:in :collection_id (map u/get-id collections)]
+                                  false)]}))
+
+(defn dump
+  "Serialized metabase instance into directory `path`."
+  [path user]
+  (mdb/setup-db!)
+  (let [users       (if user
+                      (let [user (db/select-one User
+                                   :email        user
+                                   :is_superuser true)]
+                        (assert user (trs "{0} is not a valid user" user))
+                        [user])
+                      [])
+        collections (db/select Collection
+                      {:where [:or [:= :personal_owner_id nil]
+                                   [:= :personal_owner_id (some-> users first u/get-id)]]})]
+    (dump/dump path
+               (Database)
+               (Table)
+               (field/with-values (Field))
+               (Metric)
+               (Segment)
+               collections
+               (select-entities-in-collections Card collections)
+               (select-entities-in-collections Dashboard collections)
+               (select-entities-in-collections Pulse collections)
+               users))
+  (dump/dump-settings path)
+  (dump/dump-dependencies path)
+  (dump/dump-dimensions path))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/dump.clj b/enterprise/backend/src/metabase_enterprise/serialization/dump.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3b54fdbf884e4a1054caf78589aa0c2d6ae9d61a
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/dump.clj
@@ -0,0 +1,82 @@
+(ns metabase-enterprise.serialization.dump
+  "Serialize entities into a directory structure of YAMLs."
+  (:require [clojure.java.io :as io]
+            [clojure.tools.logging :as log]
+            [metabase-enterprise.serialization
+             [names :refer [fully-qualified-name name-for-logging safe-name]]
+             [serialize :as serialize :refer [serialize]]]
+            [metabase.config :as config]
+            [metabase.models
+             [dashboard :refer [Dashboard]]
+             [database :refer [Database]]
+             [dependency :refer [Dependency]]
+             [dimension :refer [Dimension]]
+             [field :refer [Field]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [segment :refer [Segment]]
+             [setting :as setting]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.util
+             [date-2 :as u.date]
+             [i18n :as i18n :refer [trs]]]
+            [yaml
+             [core :as yaml]
+             [writer :as y.writer]])
+  (:import java.time.temporal.Temporal))
+
+(extend-type Temporal y.writer/YAMLWriter
+  (encode [data]
+    (u.date/format data)))
+
+(defn- spit-yaml
+  [filename obj]
+  (io/make-parents filename)
+  (spit filename (yaml/generate-string obj :dumper-options {:flow-style :block})))
+
+(def ^:private as-file?
+  (comp (set (map type [Pulse Dashboard Metric Segment Field User])) type))
+
+(defn dump
+  "Serialize entities into a directory structure of YAMLs at `path`."
+  [path & entities]
+  (doseq [entity (flatten entities)]
+    (try
+      (spit-yaml (if (as-file? entity)
+                   (format "%s%s.yaml" path (fully-qualified-name entity))
+                   (format "%s%s/%s.yaml" path (fully-qualified-name entity) (safe-name entity)))
+                 (serialize entity))
+      (catch Throwable _
+        (log/error (trs "Error dumping {0}" (name-for-logging entity))))))
+  (spit-yaml (str path "/manifest.yaml")
+             {:serialization-version serialize/serialization-protocol-version
+              :metabase-version      config/mb-version-info}))
+
+(defn dump-dependencies
+  "Combine all dependencies into a vector and dump it into YAML at `path`."
+  [path]
+  (spit-yaml (str path "/dependencies.yaml") (map serialize (Dependency))))
+
+(defn dump-settings
+  "Combine all settings into a map and dump it into YAML at `path`."
+  [path]
+  (spit-yaml (str path "/settings.yaml")
+             (into {} (for [{:keys [key value]} (setting/all :getter setting/get-string)]
+                        [key value]))))
+
+(defn dump-dimensions
+  "Combine all dimensions into a vector and dump it into YAML at in the directory for the
+   corresponding schema starting at `path`."
+  [path]
+  (doseq [[table-id dimensions] (group-by (comp :table_id Field :field_id) (Dimension))
+          :let [table (Table table-id)]]
+    (spit-yaml (if (:schema table)
+                 (format "%s%s/schemas/%s/dimensions.yaml"
+                         path
+                         (->> table :db_id (fully-qualified-name Database))
+                         (:schema table))
+                 (format "%s%s/dimensions.yaml"
+                         path
+                         (->> table :db_id (fully-qualified-name Database))))
+               (map serialize dimensions))))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/load.clj b/enterprise/backend/src/metabase_enterprise/serialization/load.clj
new file mode 100644
index 0000000000000000000000000000000000000000..2ad5d282aeee8da46c5ce9ff7a70e3c7734cdd1b
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/load.clj
@@ -0,0 +1,344 @@
+(ns metabase-enterprise.serialization.load
+  "Load entities serialized by `metabase-enterprise.serialization.dump`."
+  (:refer-clojure :exclude [load])
+  (:require [clojure.java.io :as io]
+            [clojure.string :as str]
+            [clojure.tools.logging :as log]
+            [metabase
+             [config :as config]
+             [util :as u]]
+            [metabase-enterprise.serialization
+             [names :refer [fully-qualified-name->context]]
+             [upsert :refer [maybe-upsert-many!]]]
+            [metabase.mbql
+             [normalize :as mbql.normalize]
+             [util :as mbql.util]]
+            [metabase.models
+             [card :refer [Card]]
+             [collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
+             [dashboard-card :refer [DashboardCard]]
+             [dashboard-card-series :refer [DashboardCardSeries]]
+             [database :as database :refer [Database]]
+             [dependency :refer [Dependency]]
+             [dimension :refer [Dimension]]
+             [field :refer [Field]]
+             [field-values :refer [FieldValues]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [pulse-card :refer [PulseCard]]
+             [pulse-channel :refer [PulseChannel]]
+             [segment :refer [Segment]]
+             [setting :as setting]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.query-processor.util :as qp.util]
+            [metabase.util
+             [date-2 :as u.date]
+             [i18n :refer [trs]]]
+            [toucan.db :as db]
+            [yaml
+             [core :as yaml]
+             [reader :as y.reader]])
+  (:import java.time.temporal.Temporal))
+
+(extend-type Temporal y.reader/YAMLReader
+  (decode [data]
+    (u.date/parse data)))
+
+(defn- slurp-dir
+  [path]
+  (doall
+   (for [^java.io.File file (.listFiles ^java.io.File (io/file path))
+         :when (-> file (.getName) (str/ends-with? ".yaml"))]
+     (yaml/from-file file true))))
+
+(defn- slurp-many
+  [paths]
+  (apply concat (map slurp-dir paths)))
+
+(defn- list-dirs
+  [path]
+  (for [^java.io.File file (.listFiles ^java.io.File (io/file path))
+        :when (.isDirectory file)]
+    (.getPath file)))
+
+(defn- mbql-fully-qualified-names->ids
+  [entity]
+  (mbql.util/replace (mbql.normalize/normalize-tokens entity)
+    [:field-id (fully-qualified-name :guard string?)]
+    [:field-id (:field (fully-qualified-name->context fully-qualified-name))]
+
+    [:metric (fully-qualified-name :guard string?)]
+    [:metric (:metric (fully-qualified-name->context fully-qualified-name))]
+
+    [:segment (fully-qualified-name :guard string?)]
+    [:segment (:segment (fully-qualified-name->context fully-qualified-name))]))
+
+(def ^:private default-user (delay
+                             (let [user (db/select-one-id User :is_superuser true)]
+                               (assert user (trs "No admin users found! At least one admin user is needed to act as the owner for all the loaded entities."))
+                               user)))
+
+(defn- terminal-dir
+  "Return the last path component (presumably a dir)"
+  [path]
+  (.getName (clojure.java.io/file path)))
+
+(defmulti load
+  "Load an entity of type `model` stored at `path` in the context `context`.
+
+   Passing in parent entities as context instead of decoding them from the path each time,
+   saves a lot of queriying."
+  (fn [path _]
+    (terminal-dir path)))
+
+(defn- load-dimensions
+  [path context]
+  (maybe-upsert-many! context Dimension
+    (for [dimension (yaml/from-file (str path "/dimensions.yaml") true)]
+      (-> dimension
+          (update :human_readable_field_id (comp :field fully-qualified-name->context))
+          (update :field_id (comp :field fully-qualified-name->context))))))
+
+(defmethod load "databases"
+  [path context]
+  (doseq [path (list-dirs path)]
+    ;; If we failed to load the DB no use in trying to load its tables
+    (when-let [db (first (maybe-upsert-many! context Database (slurp-dir path)))]
+      (doseq [inner-path (conj (list-dirs (str path "/schemas")) path)
+              :let [context (merge context {:database db
+                                            :schema   (when (not= inner-path path)
+                                                        (terminal-dir path))})]]
+        (load (str inner-path "/tables") context)
+        (load-dimensions inner-path context)))))
+
+(defmethod load "tables"
+  [path context]
+  (let [paths     (list-dirs path)
+        table-ids (maybe-upsert-many! context Table
+                    (for [table (slurp-many paths)]
+                      (assoc table :db_id (:database context))))]
+    ;; First load fields ...
+    (doseq [[path table-id] (map vector paths table-ids)
+            :when table-id]
+      (let [context (assoc context :table table-id)]
+        (load (str path "/fields") context)))
+    ;; ... then everything else so we don't have issues with cross-table referencess
+    (doseq [[path table-id] (map vector paths table-ids)
+            :when table-id]
+      (let [context (assoc context :table table-id)]
+        (load (str path "/fks") context)
+        (load (str path "/metrics") context)
+        (load (str path "/segments") context)))))
+
+(def ^:private fully-qualified-name->card-id
+  (comp :card fully-qualified-name->context))
+
+(defn- load-fields
+  [path context]
+  (let [fields       (slurp-dir path)
+        field-values (map :values fields)
+        field-ids    (maybe-upsert-many! context Field
+                       (for [field fields]
+                         (-> field
+                             (update :parent_id (comp :field fully-qualified-name->context))
+                             (update :last_analyzed u.date/parse)
+                             (update :fk_target_field_id (comp :field fully-qualified-name->context))
+                             (dissoc :values)
+                             (assoc :table_id (:table context)))))]
+    (maybe-upsert-many! context FieldValues
+      (for [[field-value field-id] (map vector field-values field-ids)
+            :when field-id]
+        (assoc field-value :field_id field-id)))))
+
+(defmethod load "fields"
+  [path context]
+  (load-fields path context))
+
+(defmethod load "fks"
+  [path context]
+  (load-fields path context))
+
+(defmethod load "metrics"
+  [path context]
+  (maybe-upsert-many! context Metric
+    (for [metric (slurp-dir path)]
+      (-> metric
+          (assoc :table_id   (:table context)
+                 :creator_id @default-user)
+          (assoc-in [:definition :source-table] (:table context))
+          (update :definition mbql-fully-qualified-names->ids)))))
+
+(defmethod load "segments"
+  [path context]
+  (maybe-upsert-many! context Segment
+    (for [metric (slurp-dir path)]
+      (-> metric
+          (assoc :table_id   (:table context)
+                 :creator_id @default-user)
+          (assoc-in [:definition :source-table] (:table context))
+          (update :definition mbql-fully-qualified-names->ids)))))
+
+(defn- update-parameter-mappings
+  [parameter-mappings]
+  (for [parameter-mapping parameter-mappings]
+    (-> parameter-mapping
+        (update :card_id fully-qualified-name->card-id)
+        (update :target mbql-fully-qualified-names->ids))))
+
+(defmethod load "dashboards"
+  [path context]
+  (let [dashboards         (slurp-dir path)
+        dashboard-ids      (maybe-upsert-many! context Dashboard
+                             (for [dashboard dashboards]
+                               (-> dashboard
+                                   (dissoc :dashboard_cards)
+                                   (assoc :collection_id (:collection context)
+                                          :creator_id    @default-user))))
+        dashboard-cards    (map :dashboard_cards dashboards)
+        dashboard-card-ids (maybe-upsert-many! context DashboardCard
+                             (for [[dashboard-cards dashboard-id] (map vector dashboard-cards dashboard-ids)
+                                   dashboard-card dashboard-cards
+                                   :when dashboard-id]
+                               (-> dashboard-card
+                                   (dissoc :series)
+                                   (update :card_id fully-qualified-name->card-id)
+                                   (assoc :dashboard_id dashboard-id)
+                                   (update :parameter_mappings update-parameter-mappings))))]
+    (maybe-upsert-many! context DashboardCardSeries
+      (for [[series dashboard-card-id] (map vector (mapcat (partial map :series) dashboard-cards)
+                                                   dashboard-card-ids)
+            dashboard-card-series series
+            :when (and dashboard-card-series dashboard-card-id)]
+        (-> dashboard-card-series
+            (assoc :dashboardcard_id dashboard-card-id)
+            (update :card_id fully-qualified-name->card-id))))))
+
+(defmethod load "pulses"
+  [path context]
+  (let [pulses    (slurp-dir path)
+        cards     (map :cards pulses)
+        channels  (map :channels pulses)
+        pulse-ids (maybe-upsert-many! context Pulse
+                    (for [pulse pulses]
+                      (-> pulse
+                          (assoc :collection_id (:collection context)
+                                 :creator_id    @default-user)
+                          (dissoc :channels :cards))))]
+    (maybe-upsert-many! context PulseCard
+      (for [[cards pulse-id] (map vector cards pulse-ids)
+            card             cards
+            :when pulse-id]
+        (-> card
+            (assoc :pulse_id pulse-id)
+            (update :card_id fully-qualified-name->card-id))))
+    (maybe-upsert-many! context PulseChannel
+      (for [[channels pulse-id] (map vector channels pulse-ids)
+            channel             channels
+            :when pulse-id]
+        (assoc channel :pulse_id pulse-id)))))
+
+(defn- source-table
+  [source-table]
+  (let [{:keys [card table]} (fully-qualified-name->context source-table)]
+    (if card
+      (str "card__" card)
+      table)))
+
+(defmethod load "cards"
+  [path context]
+  (let [paths (list-dirs path)]
+    (maybe-upsert-many! context Card
+      (for [card (slurp-many paths)]
+        (-> card
+            (update :table_id (comp :table fully-qualified-name->context))
+            (update :database_id (comp :database fully-qualified-name->context))
+            (update :dataset_query mbql-fully-qualified-names->ids)
+            (assoc :creator_id    @default-user
+                   :collection_id (:collection context))
+            (update-in [:dataset_query :database] (comp :database fully-qualified-name->context))
+            (cond->
+                (-> card
+                    :dataset_query
+                    :type
+                    qp.util/normalize-token
+                    (= :query)) (update-in [:dataset_query :query :source-table] source-table)))))
+    ;; Nested cards
+    (doseq [path paths]
+      (load (str path "/cards") context))))
+
+(defmethod load "users"
+  [path context]
+  ;; Currently we only serialize the new owner user, so it's fine to ignore mode setting
+  (maybe-upsert-many! context User
+    (for [user (slurp-dir path)]
+      (assoc user :password "changeme"))))
+
+(defn- derive-location
+  [context]
+  (if-let [parent-id (:collection context)]
+    (str (-> parent-id Collection :location) parent-id "/")
+    "/"))
+
+(defmethod load "collections"
+  [path context]
+  (doseq [path (list-dirs path)]
+    (let [context (assoc context
+                    :collection (->> (slurp-dir path)
+                                     (map (fn [collection]
+                                            (assoc collection :location (derive-location context))))
+                                     (maybe-upsert-many! context Collection)
+                                     first))]
+      (load (str path "/collections") context)
+      (load (str path "/cards") context)
+      (load (str path "/pulses") context)
+      (load (str path "/dashboards") context))))
+
+(defn load-settings
+  "Load a dump of settings."
+  [path context]
+  (doseq [[k v] (yaml/from-file (str path "/settings.yaml") true)
+          :when (or (= context :update)
+                    (nil? (setting/get-string k)))]
+    (setting/set-string! k v)))
+
+(defn- log-or-die
+  [on-error message]
+  (if (= on-error :abort)
+    (throw (Exception. (str message)))
+    (log/error message)))
+
+(defn load-dependencies
+  "Load a dump of dependencies."
+  [path context]
+  (let [fully-qualified-name->entity (comp (some-fn (comp Card :card)
+                                                    (comp Metric :metric)
+                                                    (comp Segment :segment)
+                                                    (comp Pulse :pulse))
+                                           fully-qualified-name->context)]
+    (maybe-upsert-many! context Dependency
+      (for [{:keys [model_id dependent_on_id]} (yaml/from-file (str path "/dependencies.yaml") true)]
+        (let [model        (fully-qualified-name->entity model_id)
+              dependent-on (fully-qualified-name->entity dependent_on_id)]
+          (cond
+            (and model dependent-on)
+            {:model              (name model)
+             :model_id           (u/get-id model)
+             :dependent_on_model (name dependent-on)
+             :dependent_on_id    (u/get-id dependent-on)
+             :created_at         (java.util.Date.)}
+
+            (nil? model)
+            (log-or-die (:on-error model) (trs "Error loading dependencies: reference to an unknown entitiy {0}" model_id))
+
+            (nil? dependent-on)
+            (log-or-die (:on-error model) (trs "Error loading dependencies: reference to an unknown entitiy {0}" dependent_on_id))))))))
+
+(defn compatible?
+  "Is dump at path `path` compatible with the currently running version of Metabase?"
+  [path]
+  (-> (str path "/manifest.yaml")
+      (yaml/from-file true)
+      :metabase-version
+      (= config/mb-version-info)))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/names.clj b/enterprise/backend/src/metabase_enterprise/serialization/names.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fce0b7e5dd3705ed02ab94ad370e7dcfc83bc572
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/names.clj
@@ -0,0 +1,243 @@
+(ns metabase-enterprise.serialization.names
+  "Consistent instance-independent naming scheme that replaces IDs with human-readable paths."
+  (:require [clojure.string :as str]
+            [metabase.mbql.schema :as mbql.s]
+            [metabase.models
+             [card :refer [Card]]
+             [collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
+             [database :as database :refer [Database]]
+             [field :refer [Field]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [segment :refer [Segment]]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.query-processor.util :as qp.util]
+            [metabase.util
+             [i18n :as i18n :refer [trs]]
+             [schema :as su]]
+            [ring.util.codec :as codec]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(defn safe-name
+  "Return entity name URL encoded except that spaces are retained."
+  [entity]
+  (some-> entity ((some-fn :email :name)) codec/url-encode (str/replace "%20" " ")))
+
+(def unescape-name
+  "Inverse of `safe-name`."
+  codec/url-decode)
+
+(defmulti ^:private fully-qualified-name* type)
+
+(def ^{:arglists '([entity] [model id])} fully-qualified-name
+  "Get the logical path for entity `entity`."
+  (memoize (fn
+             ([entity] (fully-qualified-name* entity))
+             ([model id]
+              (if (string? id)
+                id
+                (fully-qualified-name* (db/select-one model :id id)))))))
+
+(defmethod fully-qualified-name* (type Database)
+  [db]
+  (str "/databases/" (safe-name db)))
+
+(defmethod fully-qualified-name* (type Table)
+  [table]
+  (if (:schema table)
+    (format "%s/schemas/%s/tables/%s"
+            (->> table :db_id (fully-qualified-name Database))
+            (:schema table)
+            (safe-name table))
+    (format "%s/tables/%s"
+            (->> table :db_id (fully-qualified-name Database))
+            (safe-name table))))
+
+(defmethod fully-qualified-name* (type Field)
+  [field]
+  (if (:fk_target_field_id field)
+    (str (->> field :table_id (fully-qualified-name Table)) "/fks/" (safe-name field))
+    (str (->> field :table_id (fully-qualified-name Table)) "/fields/" (safe-name field))))
+
+(defmethod fully-qualified-name* (type Metric)
+  [metric]
+  (str (->> metric :table_id (fully-qualified-name Table)) "/metrics/" (safe-name metric)))
+
+(defmethod fully-qualified-name* (type Segment)
+  [segment]
+  (str (->> segment :table_id (fully-qualified-name Table)) "/segments/" (safe-name segment)))
+
+(defmethod fully-qualified-name* (type Collection)
+  [collection]
+  (let [parents (some->> (str/split (:location collection) #"/")
+                         rest
+                         not-empty
+                         (map #(-> % Integer/parseInt Collection safe-name (str "/collections")))
+                         (str/join "/")
+                         (format "%s/"))]
+    (str "/collections/root/collections/" parents (safe-name collection))))
+
+(defmethod fully-qualified-name* (type Dashboard)
+  [dashboard]
+  (format "%s/dashboards/%s"
+          (or (some->> dashboard :collection_id (fully-qualified-name Collection))
+              "/collections/root")
+          (safe-name dashboard)))
+
+(defmethod fully-qualified-name* (type Pulse)
+  [pulse]
+  (format "%s/pulses/%s"
+          (or (some->> pulse :collection_id (fully-qualified-name Collection))
+              "/collections/root")
+          (safe-name pulse)))
+
+(defmethod fully-qualified-name* (type Card)
+  [card]
+  (format "%s/cards/%s"
+          (or (some->> card
+                       :dataset_query
+                       qp.util/query->source-card-id
+                       (fully-qualified-name Card))
+              (some->> card
+                       :collection_id
+                       (fully-qualified-name Collection))
+              "/collections/root")
+          (safe-name card)))
+
+(defmethod fully-qualified-name* (type User)
+  [user]
+  (str "/users/" (:email user)))
+
+(defmethod fully-qualified-name* nil
+  [_]
+  nil)
+
+;; All the references in the dumps should resolved to entities already loaded.
+(def ^:private Context
+  {(s/optional-key :database)   su/IntGreaterThanZero
+   (s/optional-key :table)      su/IntGreaterThanZero
+   (s/optional-key :schema)     (s/maybe s/Str)
+   (s/optional-key :field)      su/IntGreaterThanZero
+   (s/optional-key :metric)     su/IntGreaterThanZero
+   (s/optional-key :segment)    su/IntGreaterThanZero
+   (s/optional-key :card)       su/IntGreaterThanZero
+   (s/optional-key :dashboard)  su/IntGreaterThanZero
+   (s/optional-key :collection) (s/maybe su/IntGreaterThanZero) ; root collection
+   (s/optional-key :pulse)      su/IntGreaterThanZero
+   (s/optional-key :user)       su/IntGreaterThanZero})
+
+(defmulti ^:private path->context* (fn [_ model _]
+                                     model))
+
+(def ^:private ^{:arglists '([context model entity-name])} path->context
+  "Extract entities from a logical path."
+  (memoize path->context*))
+
+(defmethod path->context* "databases"
+  [context _ db-name]
+  (assoc context :database (if (= db-name "__virtual")
+                             mbql.s/saved-questions-virtual-database-id
+                             (db/select-one-id Database :name db-name))))
+
+(defmethod path->context* "schemas"
+  [context _ schema]
+  (assoc context :schema schema))
+
+(defmethod path->context* "tables"
+  [context _ table-name]
+  (assoc context :table (db/select-one-id Table
+                          :db_id  (:database context)
+                          :schema (:schema context)
+                          :name   table-name)))
+
+(defmethod path->context* "fields"
+  [context _ field-name]
+  (assoc context :field (db/select-one-id Field
+                          :table_id (:table context)
+                          :name     field-name)))
+
+(defmethod path->context* "fks"
+  [context _ field-name]
+  (path->context* context "fields" field-name))
+
+(defmethod path->context* "metrics"
+  [context _ metric-name]
+  (assoc context :metric (db/select-one-id Metric
+                           :table_id (:table context)
+                           :name     metric-name)))
+
+(defmethod path->context* "segments"
+  [context _ segment-name]
+  (assoc context :segment (db/select-one-id Segment
+                            :table_id (:table context)
+                            :name     segment-name)))
+
+(defmethod path->context* "collections"
+  [context _ collection-name]
+  (if (= collection-name "root")
+    (assoc context :collection nil)
+    (assoc context :collection (db/select-one-id Collection
+                                 :name     collection-name
+                                 :location (or (some-> context
+                                                       :collection
+                                                       Collection
+                                                       :location
+                                                       (str (:collection context) "/"))
+                                               "/")))))
+
+(defmethod path->context* "dashboards"
+  [context _ dashboard-name]
+  (assoc context :dashboard (db/select-one-id Dashboard
+                              :collection_id (:collection context)
+                              :name          dashboard-name)))
+
+(defmethod path->context* "pulses"
+  [context _ pulse-name]
+  (assoc context :dashboard (db/select-one-id Pulse
+                              :collection_id (:collection context)
+                              :name          pulse-name)))
+
+(defmethod path->context* "cards"
+  [context _ dashboard-name]
+  (assoc context :card (db/select-one-id Card
+                         :collection_id (:collection context)
+                         :name          dashboard-name)))
+
+(defmethod path->context* "users"
+  [context _ email]
+  (assoc context :user (db/select-one-id User
+                         :email email)))
+
+(def ^:private separator-pattern #"\/")
+
+(defn fully-qualified-name->context
+  "Parse a logcial path into a context map."
+  [fully-qualified-name]
+  (when fully-qualified-name
+    (let [context (->> (str/split fully-qualified-name separator-pattern)
+                       rest ; we start with a /
+                       (partition 2)
+                       (reduce (fn [context [model entity-name]]
+                                 (path->context context model (unescape-name entity-name)))
+                               {}))]
+      (try
+        (s/validate (s/maybe Context) context)
+        (catch Exception e
+          (ex-info (trs "Can''t resolve {0} in fully qualified name {1}"
+                        (str/join ", " (map name (keys (:value (ex-data e)))))
+                        fully-qualified-name)
+            {:fully-qualified-name fully-qualified-name
+             :context              context}))))))
+
+(defn name-for-logging
+  "Return a string representation of entity suitable for logs"
+  ([entity] (name-for-logging (name entity) entity))
+  ([model {:keys [name id]}]
+   (cond
+     (and name id) (format "%s \"%s\" (ID %s)" model name id)
+     name          (format "%s \"%s\"" model name)
+     id            (format "%s %s" model id)
+     :else         model)))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/serialize.clj b/enterprise/backend/src/metabase_enterprise/serialization/serialize.clj
new file mode 100644
index 0000000000000000000000000000000000000000..8a4c324da552e0989b1e2b6f2d9d92b72e7f4946
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/serialize.clj
@@ -0,0 +1,167 @@
+(ns metabase-enterprise.serialization.serialize
+  "Transform entity into a form suitable for serialization."
+  (:require [clojure.string :as str]
+            [medley.core :as m]
+            [metabase-enterprise.serialization.names :refer [fully-qualified-name]]
+            [metabase.mbql
+             [normalize :as mbql.normalize]
+             [schema :as mbql.s]
+             [util :as mbql.util]]
+            [metabase.models
+             [card :refer [Card]]
+             [dashboard :refer [Dashboard]]
+             [dashboard-card :refer [DashboardCard]]
+             [dashboard-card-series :refer [DashboardCardSeries]]
+             [database :as database :refer [Database]]
+             [dependency :refer [Dependency]]
+             [dimension :refer [Dimension]]
+             [field :as field :refer [Field]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [pulse-card :refer [PulseCard]]
+             [pulse-channel :refer [PulseChannel]]
+             [segment :refer [Segment]]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.util :as u]
+            [toucan.db :as db]))
+
+(def ^:const ^Long serialization-protocol-version
+  "Current serialization protocol version.
+
+  This gets stored with each dump, so we can correctly recover old dumps."
+  1)
+
+(def ^:private ^{:arglists '([form])} mbql-entity-reference?
+  "Is given form an MBQL entity reference?"
+  (partial mbql.normalize/is-clause? #{:field-id :fk-> :metric :segment}))
+
+(defn- mbql-id->fully-qualified-name
+  [mbql]
+  (-> mbql
+      mbql.normalize/normalize-tokens
+      (mbql.util/replace
+        ;; `integer?` guard is here to make the operation idempotent
+        [:field-id (id :guard integer?)]
+        [:field-id (fully-qualified-name Field id)]
+
+        [:metric (id :guard integer?)]
+        [:metric (fully-qualified-name Metric id)]
+
+        [:segment (id :guard integer?)]
+        [:segment (fully-qualified-name Segment id)]
+
+        ;; Legacy form with raw IDs
+        [:fk-> (from :guard integer?) (to :guard integer?)]
+        [:fk-> [:field-id (fully-qualified-name Field from)]
+         [:field-id (fully-qualified-name Field to)]])))
+
+(defn- ids->fully-qualified-names
+  [entity]
+  (mbql.util/replace entity
+    mbql-entity-reference?
+    (mbql-id->fully-qualified-name &match)
+
+    map?
+    (as-> &match entity
+      (m/update-existing entity :database (fn [db-id]
+                                            (if (= db-id mbql.s/saved-questions-virtual-database-id)
+                                              "database/__virtual"
+                                              (fully-qualified-name Database db-id))))
+      (m/update-existing entity :card_id (partial fully-qualified-name Card))
+      (m/update-existing entity :source-table (fn [source-table]
+                                                (if (and (string? source-table)
+                                                         (str/starts-with? source-table "card__"))
+                                                  (fully-qualified-name Card (-> source-table
+                                                                                 (str/split #"__")
+                                                                                 second
+                                                                                 Integer/parseInt))
+                                                  (fully-qualified-name Table source-table))))
+      (m/map-vals ids->fully-qualified-names entity))))
+
+(defn- strip-crud
+  "Removes unneeded fields that can either be reconstructed from context or are meaningless
+   (eg. :created_at)."
+  [entity]
+  (cond-> (dissoc entity :id :creator_id :created_at :updated_at :db_id :location
+                  :dashboard_id :fields_hash :personal_owner_id :made_public_by_id :collection_id
+                  :pulse_id)
+    (some #(instance? % entity) (map type [Metric Field Segment])) (dissoc :table_id)))
+
+(defmulti ^:private serialize-one
+  type)
+
+(def ^{:arglists '([entity])} serialize
+  "Serialize entity `entity`."
+  (comp ids->fully-qualified-names strip-crud serialize-one))
+
+(defmethod serialize-one :default
+  [entity]
+  entity)
+
+(defmethod serialize-one (type Database)
+  [db]
+  (dissoc db :features))
+
+(defmethod serialize-one (type Field)
+  [field]
+  (let [field (-> field
+                  (update :parent_id (partial fully-qualified-name Field))
+                  (update :fk_target_field_id (partial fully-qualified-name Field)))]
+    (if (contains? field :values)
+      (update field :values u/select-non-nil-keys [:values :human_readable_values])
+      (assoc field :values (-> field
+                               field/values
+                               (u/select-non-nil-keys [:values :human_readable_values]))))))
+
+(defn- dashboard-cards-for-dashboard
+  [dashboard]
+  (let [dashboard-cards (db/select DashboardCard :dashboard_id (u/get-id dashboard))
+        series          (when (not-empty dashboard-cards)
+                          (db/select DashboardCardSeries
+                            :dashboardcard_id [:in (map u/get-id dashboard-cards)]))]
+    (for [dashboard-card dashboard-cards]
+      (-> dashboard-card
+          (assoc :series (for [series series
+                               :when (= (:dashboardcard_id series) (u/get-id dashboard-card))]
+                           (-> series
+                               (update :card_id (partial fully-qualified-name Card))
+                               (dissoc :id :dashboardcard_id))))
+          strip-crud))))
+
+(defmethod serialize-one (type Dashboard)
+  [dashboard]
+  (assoc dashboard :dashboard_cards (dashboard-cards-for-dashboard dashboard)))
+
+(defmethod serialize-one (type Card)
+  [card]
+  (-> card
+      (m/update-existing :table_id (partial fully-qualified-name Table))
+      (update :database_id (partial fully-qualified-name Database))))
+
+(defmethod serialize-one (type Pulse)
+  [pulse]
+  (assoc pulse
+    :cards    (for [card (db/select PulseCard :pulse_id (u/get-id pulse))]
+                (-> card
+                    (dissoc :id :pulse_id)
+                    (update :card_id (partial fully-qualified-name Card))))
+    :channels (for [channel (db/select PulseChannel :pulse_id (u/get-id pulse))]
+                (strip-crud channel))))
+
+(defmethod serialize-one (type User)
+  [user]
+  (select-keys user [:first_name :last_name :email :is_superuser]))
+
+(defmethod serialize-one (type Dimension)
+  [dimension]
+  (-> dimension
+      (update :field_id (partial fully-qualified-name Field))
+      (update :human_readable_field_id (partial fully-qualified-name Field))))
+
+(defmethod serialize-one (type Dependency)
+  [dependency]
+  (-> dependency
+      (select-keys [:dependent_on_id :model_id])
+      (update :dependent_on_id (partial fully-qualified-name (-> dependency :dependent_on_model symbol)))
+      (update :model_id (partial fully-qualified-name (-> dependency :model symbol)))))
diff --git a/enterprise/backend/src/metabase_enterprise/serialization/upsert.clj b/enterprise/backend/src/metabase_enterprise/serialization/upsert.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c6cdb28d5c53970d0c5634874f080e9867520b3b
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/serialization/upsert.clj
@@ -0,0 +1,141 @@
+(ns metabase-enterprise.serialization.upsert
+  "Upsert-or-skip functionality for our models."
+  (:require [cheshire.core :as json]
+            [clojure.data :as diff]
+            [clojure.tools.logging :as log]
+            [medley.core :as m]
+            [metabase-enterprise.serialization.names :refer [name-for-logging]]
+            [metabase.models
+             [card :refer [Card]]
+             [collection :refer [Collection]]
+             [dashboard :refer [Dashboard]]
+             [dashboard-card :refer [DashboardCard]]
+             [dashboard-card-series :refer [DashboardCardSeries]]
+             [database :as database :refer [Database]]
+             [dependency :refer [Dependency]]
+             [dimension :refer [Dimension]]
+             [field :refer [Field]]
+             [field-values :refer [FieldValues]]
+             [metric :refer [Metric]]
+             [pulse :refer [Pulse]]
+             [pulse-card :refer [PulseCard]]
+             [pulse-channel :refer [PulseChannel]]
+             [segment :refer [Segment]]
+             [setting :as setting :refer [Setting]]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.util :as u]
+            [metabase.util.i18n :as i18n :refer [trs]]
+            [toucan
+             [db :as db]
+             [models :as models]]))
+
+(def ^:private identity-condition
+  {Database            [:name]
+   Table               [:schema :name :db_id]
+   Field               [:name :table_id]
+   Metric              [:name :table_id]
+   Segment             [:name :table_id]
+   Collection          [:name :location]
+   Dashboard           [:name :collection_id]
+   DashboardCard       [:card_id :dashboard_id :visualiation_settings]
+   DashboardCardSeries [:dashboardcard_id :card_id]
+   FieldValues         [:field_id]
+   Dimension           [:field_id :human_readable_field_id]
+   Dependency          [:model_id :model :dependent_on_model :dependent_on_id]
+   Setting             [:key]
+   Pulse               [:name :collection_id]
+   PulseCard           [:pulse_id :card_id]
+   PulseChannel        [:pulse_id :channel_type :details]
+   Card                [:name :collection_id]
+   User                [:email]})
+
+;; This could potentially be unrolled into one giant select
+(defn- select-identical
+  [model entity]
+  (->> (or (identity-condition model)
+           (throw (ex-info (trs "Model {0} does not support upsert" model) {:model model})))
+       (select-keys entity)
+       (m/map-vals (fn [v]
+                     (if (coll? v)
+                       (json/encode v)
+                       v)))
+       (m/mapply db/select-one model)))
+
+(defn- has-post-insert?
+  [model]
+  (not= (find-protocol-method models/IModel :post-insert model) identity))
+
+(defmacro with-error-handling
+  "Execute body and catch and log any exceptions doing so throws."
+  [message & body]
+  `(try
+     (do ~@body)
+     (catch Throwable e#
+       (log/error (u/format-color 'red "%s: %s" ~message (.getMessage e#)))
+       nil)))
+
+(defn- insert-many-individually!
+  [model on-error entities]
+  (for [entity entities]
+    (when-let [entity (if (= :abort on-error)
+                        (db/insert! model entity)
+                        (with-error-handling
+                          (trs "Error inserting {0}" (name-for-logging model entity))
+                          (db/insert! model entity)))]
+      (u/get-id entity))))
+
+(defn- maybe-insert-many!
+  [model on-error entities]
+  (if (has-post-insert? model)
+    (insert-many-individually! model on-error entities)
+    (if (= :abort on-error)
+      (db/insert-many! model entities)
+      (try
+        (db/insert-many! model entities)
+        ;; Retry each individually so we can do as much as we can
+        (catch Throwable _
+          (insert-many-individually! model on-error entities))))))
+
+(defn maybe-upsert-many!
+  "Batch upsert-or-skip"
+  [{:keys [mode on-error]} model entities]
+  (let [same?                        (comp nil? second diff/diff)
+        {:keys [update insert skip]} (->> entities
+                                          (map-indexed (fn [position entity]
+                                                         [position
+                                                          entity
+                                                          (select-identical model entity)]))
+                                          (group-by (fn [[_ entity existing]]
+                                                      (case mode
+                                                        :update (cond
+                                                                  (same? existing entity) :skip
+                                                                  existing                :update
+                                                                  :else                   :insert)
+                                                        :skip   (if existing
+                                                                  :skip
+                                                                  :insert)))))]
+
+    (doseq [[_ entity _] insert]
+      (log/info (trs "Inserting {0}" (name-for-logging (name model) entity))))
+    (doseq [[_ _ existing] skip]
+      (if (= mode :skip)
+        (log/info (trs "{0} already exists -- skipping"  (name-for-logging (name model) existing)))
+        (log/info (trs "Skipping {0} (nothing to update)" (name-for-logging (name model) existing)))))
+    (doseq [[_ _ existing] update]
+      (log/info (trs "Updating {0}" (name-for-logging (name model) existing))))
+
+    (->> (concat (for [[position _ existing] skip]
+                   [(u/get-id existing) position])
+                 (map vector (maybe-insert-many! model on-error (map second insert))
+                      (map first insert))
+                 (for [[position entity existing] update]
+                   (let [id (u/get-id existing)]
+                     (if (= on-error :abort)
+                       (db/update! model id entity)
+                       (with-error-handling
+                         (trs "Error updating {0}" (name-for-logging (name model) entity))
+                         (db/update! model id entity)))
+                     [id position])))
+         (sort-by second)
+         (map first))))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj b/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3d44fb13eadf0af672ead96402a993deaef70e0e
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj
@@ -0,0 +1,11 @@
+(ns metabase-enterprise.sso.api.routes
+  (:require [compojure.core :as compojure]
+            [metabase-enterprise.sso.api.sso :as sso]))
+
+;; This needs to be installed in the `metabase.routes/routes` -- not `metabase.api.routes/routes` !!!
+(compojure/defroutes ^{:doc "Ring routes for auth (SAML) API endpoints."} routes
+  (compojure/context
+   "/auth"
+   []
+   (compojure/routes
+    (compojure/context "/sso" [] sso/routes))))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/api/sso.clj b/enterprise/backend/src/metabase_enterprise/sso/api/sso.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3c93af6c2af18851280b96dda18e91e0f36e3c6c
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/api/sso.clj
@@ -0,0 +1,97 @@
+(ns metabase-enterprise.sso.api.sso
+  "`/auth/sso` Routes.
+
+  Implements the SSO routes needed for SAML and JWT. This namespace primarily provides hooks for those two backends so
+  we can have a uniform interface both via the API and code"
+  (:require [clojure.string :as str]
+            [clojure.tools.logging :as log]
+            [compojure.core :refer [GET POST]]
+            [metabase-enterprise.sso.integrations.sso-settings :as sso-settings]
+            [metabase.api.common :as api]
+            [metabase.plugins.classloader :as classloader]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.util :as u]
+            [metabase.util.i18n :refer [trs tru]]
+            [ring.util.codec :as codec]
+            [stencil.core :as stencil]))
+
+(defn- sso-backend
+  "Function that powers the defmulti in figuring out which SSO backend to use. It might be that we need to have more
+  complex logic around this, but now it's just a simple priority. If SAML is configured use that otherwise JWT"
+  [_]
+  ;; load the SSO integrations so their implementations for the multimethods below are available. Can't load in
+  ;; `:require` because it would cause a circular ref / those namespaces aren't used here at any rate
+  ;; (`cljr-clean-namespace` would remove them)
+  (classloader/require '[metabase-enterprise.sso.integrations jwt saml])
+  (cond
+    (sso-settings/saml-configured?) :saml
+    (sso-settings/jwt-enabled)      :jwt
+    :else                           nil))
+
+(defmulti sso-get
+  "Multi-method for supporting the first part of an SSO signin request. An implementation of this method will usually
+  result in a redirect to an SSO backend"
+  sso-backend)
+
+(defmulti sso-post
+  "Multi-method for supporting a POST-back from an SSO signin request. An implementation of this method will need to
+  validate the POST from the SSO backend and successfully log the user into Metabase."
+  sso-backend)
+
+(defn- throw-not-configured-error []
+  (throw (ex-info (str (tru "SSO has not been enabled and/or configured"))
+           {:status-code 400})))
+
+(defmethod sso-get :default
+  [_]
+  (throw-not-configured-error))
+
+(defmethod sso-post :default
+  [_]
+  (throw-not-configured-error))
+
+(defn- throw-if-no-metastore-token []
+  (when-not (metastore/enable-sso?)
+    (throw (ex-info (str (tru "SSO requires a valid token"))
+             {:status-code 403}))))
+
+(api/defendpoint GET "/"
+  "SSO entry-point for an SSO user that has not logged in yet"
+  {:as req}
+  (throw-if-no-metastore-token)
+  (try
+    (sso-get req)
+    (catch Throwable e
+      (log/error #_e (trs "Error returning SSO entry point"))
+      (throw e))))
+
+(defn- sso-error-page [^Throwable e]
+  {:status  (get (ex-data e) :status-code 500)
+   :headers {"Content-Type" "text/html"}
+   :body    (stencil/render-file "metabase_enterprise/sandbox/api/error_page"
+              (let [message    (.getMessage e)
+                    stacktrace (u/pprint-to-str (vec (.getStackTrace e)))
+                    data       (u/pprint-to-str (ex-data e))]
+                {:mailto         (str "mailto:support@metabase.com"
+                                      (str "?subject=" (codec/url-encode (str "[Login Error] " message)))
+                                      (str "&body=" (codec/url-encode
+                                                     (str/join "\n" ["Stacktrace:"
+                                                                     stacktrace
+                                                                     "Additional Info:"
+                                                                     data]))))
+                 :errorMessage   message
+                 :exceptionClass (.getName Exception)
+                 :stacktrace     stacktrace
+                 :additionalData data}))})
+
+(api/defendpoint POST "/"
+  "Route the SSO backends call with successful login details"
+  {:as req}
+  (throw-if-no-metastore-token)
+  (try
+    (sso-post req)
+    (catch Throwable e
+      (log/error e (trs "Error logging in"))
+      (sso-error-page e))))
+
+(api/define-routes)
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e50555480a2d68bb92f739039dcbc219853556de
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj
@@ -0,0 +1,85 @@
+(ns metabase-enterprise.sso.integrations.jwt
+  "Implementation of the JWT backend for sso"
+  (:require [buddy.sign.jwt :as jwt]
+            [metabase-enterprise.sso.api.sso :as sso]
+            [metabase-enterprise.sso.integrations
+             [sso-settings :as sso-settings]
+             [sso-utils :as sso-utils]]
+            [metabase.api
+             [common :as api]
+             [session :as session]]
+            [metabase.integrations.common :as integrations.common]
+            [metabase.middleware.session :as mw.session]
+            [metabase.util.i18n :refer [tru]]
+            [ring.util.response :as resp])
+  (:import java.net.URLEncoder))
+
+(defn fetch-or-create-user!
+  "Returns a session map for the given `email`. Will create the user if needed."
+  [first-name last-name email user-attributes]
+  (when-not (sso-settings/jwt-configured?)
+    (throw (IllegalArgumentException. (str (tru "Can't create new JWT user when JWT is not configured")))))
+  (or (sso-utils/fetch-and-update-login-attributes! email user-attributes)
+      (sso-utils/create-new-sso-user! {:first_name       first-name
+                                       :last_name        last-name
+                                       :email            email
+                                       :sso_source       "jwt"
+                                       :login_attributes user-attributes})))
+
+(def ^:private ^{:arglists '([])} jwt-attribute-email     (comp keyword sso-settings/jwt-attribute-email))
+(def ^:private ^{:arglists '([])} jwt-attribute-firstname (comp keyword sso-settings/jwt-attribute-firstname))
+(def ^:private ^{:arglists '([])} jwt-attribute-lastname  (comp keyword sso-settings/jwt-attribute-lastname))
+(def ^:private ^{:arglists '([])} jwt-attribute-groups    (comp keyword sso-settings/jwt-attribute-groups))
+
+(defn- jwt-data->login-attributes [jwt-data]
+  (dissoc jwt-data
+          (jwt-attribute-email)
+          (jwt-attribute-firstname)
+          (jwt-attribute-lastname)
+          :iat
+          :max_age))
+
+;; JWTs use seconds since Epoch, not milliseconds since Epoch for the `iat` and `max_age` time. 3 minutes is the time
+;; used by Zendesk for their JWT SSO, so it seemed like a good place for us to start
+(def ^:private ^:const three-minutes-in-seconds 180)
+
+(defn- group-names->ids [group-names]
+  (set (mapcat (sso-settings/jwt-group-mappings)
+               (map keyword group-names))))
+
+(defn- sync-groups! [user jwt-data]
+  (when (sso-settings/jwt-group-sync)
+    (when-let [groups-attribute (jwt-attribute-groups)]
+      (when-let [group-names (get jwt-data (jwt-attribute-groups))]
+        (integrations.common/sync-group-memberships! user (group-names->ids group-names))))))
+
+(defn- login-jwt-user
+  [jwt {{redirect :return_to} :params, :as request}]
+  (let [jwt-data     (jwt/unsign jwt (sso-settings/jwt-shared-secret)
+                                 {:max-age three-minutes-in-seconds})
+        login-attrs  (jwt-data->login-attributes jwt-data)
+        email        (get jwt-data (jwt-attribute-email))
+        first-name   (get jwt-data (jwt-attribute-firstname) "Unknown")
+        last-name    (get jwt-data (jwt-attribute-lastname) "Unknown")
+        user         (fetch-or-create-user! first-name last-name email login-attrs)
+        session      (session/create-session! :sso user)
+        redirect-url (or redirect (URLEncoder/encode "/"))]
+    (sync-groups! user jwt-data)
+    (mw.session/set-session-cookie request (resp/redirect redirect-url) session)))
+
+(defn- check-jwt-enabled []
+  (api/check (sso-settings/jwt-configured?)
+    [400 (tru "JWT SSO has not been enabled and/or configured")]))
+
+(defmethod sso/sso-get :jwt
+  [{{:keys [jwt redirect]} :params, :as request}]
+  (check-jwt-enabled)
+  (if jwt
+    (login-jwt-user jwt request)
+    (resp/redirect (str (sso-settings/jwt-identity-provider-uri)
+                        (when redirect
+                          (str "?return_to=" redirect))))))
+
+(defmethod sso/sso-post :jwt
+  [req]
+  (throw (ex-info "POST not valid for JWT SSO requests" {:status-code 400})))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj
new file mode 100644
index 0000000000000000000000000000000000000000..b11b229cf9da46203ad1caf1e3829003db45e5fa
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj
@@ -0,0 +1,164 @@
+(ns metabase-enterprise.sso.integrations.saml
+  "Implementation of the SAML backend for SSO"
+  (:require [buddy.core.codecs :as codecs]
+            [clojure.string :as str]
+            [clojure.tools.logging :as log]
+            [medley.core :as m]
+            [metabase
+             [public-settings :as public-settings]
+             [util :as u]]
+            [metabase-enterprise.sso.api.sso :as sso]
+            [metabase-enterprise.sso.integrations
+             [sso-settings :as sso-settings]
+             [sso-utils :as sso-utils]]
+            [metabase.api
+             [common :as api]
+             [session :as session]]
+            [metabase.integrations.common :as integrations.common]
+            [metabase.middleware.session :as mw.session]
+            [metabase.util.i18n :refer [trs tru]]
+            [ring.util
+             [codec :as codec]
+             [response :as resp]]
+            [saml20-clj.core :as saml]
+            [schema.core :as s])
+  (:import java.util.UUID))
+
+(defn- group-names->ids [group-names]
+  (->> (cond-> group-names (string? group-names) vector)
+       (map keyword)
+       (mapcat (sso-settings/saml-group-mappings))
+       set))
+
+(defn- sync-groups! [user group-names]
+  (when (sso-settings/saml-group-sync)
+    (when group-names
+      (integrations.common/sync-group-memberships! user (group-names->ids group-names)))))
+
+(s/defn saml-auth-fetch-or-create-user! :- (s/maybe {:id UUID, s/Keyword s/Any})
+  "Returns a Session for the given `email`. Will create the user if needed."
+  [first-name last-name email group-names user-attributes]
+  (when-not (sso-settings/saml-configured?)
+    (throw (IllegalArgumentException. "Can't create new SAML user when SAML is not configured")))
+  (when-not email
+    (throw (ex-info (str (tru "Invalid SAML configuration: could not find user email.")
+                         " "
+                         (tru "We tried looking for {0}, but couldn't find the attribute."
+                              (sso-settings/saml-attribute-email))
+                         " "
+                         (tru "Please make sure your SAML IdP is properly configured."))
+             {:status-code 400, :user-attributes (keys user-attributes)})))
+  (when-let [user (or (sso-utils/fetch-and-update-login-attributes! email user-attributes)
+                      (sso-utils/create-new-sso-user! {:first_name       first-name
+                                                       :last_name        last-name
+                                                       :email            email
+                                                       :sso_source       "saml"
+                                                       :login_attributes user-attributes}))]
+    (sync-groups! user group-names)
+    (session/create-session! :sso user)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; SAML route supporting functions
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn- acs-url []
+  (str (public-settings/site-url) "/auth/sso"))
+
+(defn- sp-cert-keystore-details []
+  (when-let [path (sso-settings/saml-keystore-path)]
+    (when-let [password (sso-settings/saml-keystore-password)]
+      (when-let [key-name (sso-settings/saml-keystore-alias)]
+        {:filename path
+         :password password
+         :alias    key-name}))))
+
+(defn- check-saml-enabled []
+  (api/check (sso-settings/saml-configured?)
+    [400 (tru "SAML has not been enabled and/or configured")]))
+
+(defmethod sso/sso-get :saml
+  ;; Initial call that will result in a redirect to the IDP along with information about how the IDP can authenticate
+  ;; and redirect them back to us
+  [req]
+  (check-saml-enabled)
+  (try
+    (let [redirect-url (or (get-in req [:params :redirect])
+                           (log/warn (trs "Warning: expected `redirect` param, but none is present"))
+                           (public-settings/site-url))
+          idp-url      (sso-settings/saml-identity-provider-uri)
+          saml-request (saml/request
+                        {:request-id (str "id-" (java.util.UUID/randomUUID))
+                         :sp-name    (sso-settings/saml-application-name)
+                         :issuer     (sso-settings/saml-application-name)
+                         :acs-url    (acs-url)
+                         :idp-url    idp-url
+                         :credential (sp-cert-keystore-details)})
+          ;; encode the redirect URL to base-64 if it's not already encoded.
+          relay-state  (cond-> redirect-url
+                         (not (u/base64-string? redirect-url)) saml/str->base64)]
+      (saml/idp-redirect-response saml-request idp-url relay-state))
+    (catch Throwable e
+      (let [msg (trs "Error generating SAML request")]
+        (log/error e msg)
+        (throw (ex-info msg {:status-code 500} e))))))
+
+(defn- validate-response [response]
+  (let [idp-cert (or (sso-settings/saml-identity-provider-certificate)
+                     (throw (ex-info (str (tru "Unable to log in: SAML IdP certificate is not set."))
+                                     {:status-code 500})))]
+    (try
+      (saml/validate response idp-cert (sp-cert-keystore-details) {:acs-url (acs-url)
+                                                                   :issuer  (sso-settings/saml-identity-provider-issuer)})
+      (catch Throwable e
+        (log/error e (trs "SAML response validation failed"))
+        (throw (ex-info (tru "Unable to log in: SAML response validation failed")
+                        {:status-code 401}
+                        e))))))
+
+(defn- xml-string->saml-response [xml-string]
+  (validate-response (saml/->Response xml-string)))
+
+(defn- unwrap-user-attributes
+  "For some reason all of the user attributes coming back from the saml library are wrapped in a list, instead of 'Ryan',
+  it's ('Ryan'). This function discards the list if there's just a single item in it."
+  [m]
+  (m/map-vals (fn [maybe-coll]
+                (if (and (coll? maybe-coll)
+                         (= 1 (count maybe-coll)))
+                  (first maybe-coll)
+                  maybe-coll))
+              m))
+
+(defn- saml-response->attributes [saml-response]
+  (let [assertions (saml/assertions saml-response)
+        attrs      (-> assertions first :attrs unwrap-user-attributes)]
+    (when-not attrs
+      (throw (ex-info (str (tru "Unable to log in: SAML info does not contain user attributes."))
+                      {:status-code 401})))
+    attrs))
+
+(defn- base64-decode [s]
+  (codecs/bytes->str (codec/base64-decode s)))
+
+(defmethod sso/sso-post :saml
+  ;; Does the verification of the IDP's response and 'logs the user in'. The attributes are available in the response:
+  ;; `(get-in saml-info [:assertions :attrs])
+  [{:keys [params], :as request}]
+  (check-saml-enabled)
+  (let [continue-url  (u/ignore-exceptions
+                        (when-let [s (some-> (:RelayState params) base64-decode)]
+                          (when-not (str/blank? s)
+                            s)))
+        xml-string    (base64-decode (:SAMLResponse params))
+        saml-response (xml-string->saml-response xml-string)
+        attrs         (saml-response->attributes saml-response)
+        email         (get attrs (sso-settings/saml-attribute-email))
+        first-name    (get attrs (sso-settings/saml-attribute-firstname) "Unknown")
+        last-name     (get attrs (sso-settings/saml-attribute-lastname) "Unknown")
+        groups        (get attrs (sso-settings/saml-attribute-group))
+        session       (saml-auth-fetch-or-create-user! first-name last-name email groups attrs)
+        response      (resp/redirect (or continue-url (public-settings/site-url)))]
+    (mw.session/set-session-cookie request response session)))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3c80d4f957378cf739bcaf5fe332d291dd6a3480
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_settings.clj
@@ -0,0 +1,166 @@
+(ns metabase-enterprise.sso.integrations.sso-settings
+  "Namesapce for defining settings used by the SSO backends. This is separate as both the functions needed to support
+  the SSO backends and the generic routing code used to determine which SSO backend to use need this
+  information. Separating out this information creates a better dependency graph and avoids circular dependencies."
+  (:require [clojure.tools.logging :as log]
+            [metabase.models.setting :as setting :refer [defsetting]]
+            [metabase.util :as u]
+            [metabase.util
+             [i18n :refer [deferred-tru trs tru]]
+             [schema :as su]]
+            [saml20-clj.core :as saml]
+            [schema.core :as s]))
+
+(def ^:private GroupMappings
+  (s/maybe {su/KeywordOrString [su/IntGreaterThanZero]}))
+
+(def ^:private ^{:arglists '([group-mappings])} validate-group-mappings
+  (s/validator GroupMappings))
+
+(defsetting saml-enabled
+  (deferred-tru "Enable SAML authentication.")
+  :type    :boolean
+  :default false)
+
+(defsetting saml-identity-provider-uri
+  (deferred-tru "This is the URL where your users go to log in to your identity provider. Depending on which IdP you''re
+using, this usually looks like https://your-org-name.example.com or https://example.com/app/my_saml_app/abc123/sso/saml"))
+
+(s/defn ^:private validate-saml-idp-cert
+  "Validate that an encoded identity provider certificate is valid, or throw an Exception."
+  [idp-cert-str :- s/Str]
+  (try
+    (instance? java.security.cert.X509Certificate (saml/->X509Certificate idp-cert-str))
+    (catch Throwable e
+      (log/error e (trs "Error parsing SAML identity provider certificate"))
+      (throw
+       (Exception. (tru "Invalid identity provider certificate. Certificate should be a base-64 encoded string."))))))
+
+(defsetting saml-identity-provider-certificate
+  (deferred-tru "Encoded certificate for the identity provider. Depending on your IdP, you might need to download this,
+open it in a text editor, then copy and paste the certificate's contents here.")
+  :setter (fn [new-value]
+            ;; when setting the idp cert validate that it's something we
+            (when new-value
+              (validate-saml-idp-cert new-value))
+            (setting/set-string! :saml-identity-provider-certificate new-value)))
+
+(defsetting saml-identity-provider-issuer
+  (deferred-tru "This is a unique identifier for the IdP. Often referred to as Entity ID or simply 'Issuer'. Depending
+on your IdP, this usually looks something like http://www.example.com/141xkex604w0Q5PN724v"))
+
+(defsetting saml-application-name
+  (deferred-tru "This application name will be used for requests to the Identity Provider")
+  :default "Metabase")
+
+(defsetting saml-keystore-path
+  (deferred-tru "Absolute path to the Keystore file to use for signing SAML requests"))
+
+(defsetting saml-keystore-password
+  (deferred-tru "Password for opening the keystore")
+  :default "changeit"
+  :sensitive? true)
+
+(defsetting saml-keystore-alias
+  (deferred-tru "Alias for the key that Metabase should use for signing SAML requests")
+  :default "metabase")
+
+(defsetting saml-attribute-email
+  (deferred-tru "SAML attribute for the user''s email address")
+  :default "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
+
+(defsetting saml-attribute-firstname
+  (deferred-tru "SAML attribute for the user''s first name")
+  :default "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")
+
+(defsetting saml-attribute-lastname
+  (deferred-tru "SAML attribute for the user''s last name")
+  :default "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname")
+
+(defsetting saml-group-sync
+  (deferred-tru "Enable group membership synchronization with SAML.")
+  :type    :boolean
+  :default false)
+
+(defsetting saml-attribute-group
+  (deferred-tru "SAML attribute for group syncing")
+  :default "member_of")
+
+(defsetting saml-group-mappings
+  ;; Should be in the form: {"groupName": [1, 2, 3]} where keys are SAML groups and values are lists of MB groups IDs
+  (deferred-tru "JSON containing SAML to Metabase group mappings.")
+  :type    :json
+  :default {}
+  :setter (comp (partial setting/set-json! :saml-group-mappings) validate-group-mappings))
+
+(defn saml-configured?
+  "Check if SAML is enabled and that the mandatory settings are configured."
+  []
+  (boolean (and (saml-enabled)
+                (saml-identity-provider-uri)
+                (saml-identity-provider-certificate))))
+
+(defsetting jwt-enabled
+  (deferred-tru "Enable JWT based authentication")
+  :type    :boolean
+  :default false)
+
+(defsetting jwt-identity-provider-uri
+  (deferred-tru "URL of JWT based login page"))
+
+(defsetting jwt-shared-secret
+  (deferred-tru "String used to seed the private key used to validate JWT messages")
+  :setter (fn [new-value]
+            (when (seq new-value)
+              (assert (u/hexadecimal-string? new-value)
+                       "Invalid JWT Shared Secret key must be a hexadecimal-encoded 256-bit key (i.e., a 64-character string)."))
+            (setting/set-string! :jwt-shared-secret new-value)))
+
+(defsetting jwt-attribute-email
+  (deferred-tru "Key to retrieve the JWT user's email address")
+  :default "email")
+
+(defsetting jwt-attribute-firstname
+  (deferred-tru "Key to retrieve the JWT user's first name")
+  :default "first_name")
+
+(defsetting jwt-attribute-lastname
+  (deferred-tru "Key to retrieve the JWT user's last name")
+  :default "last_name")
+
+(defsetting jwt-attribute-groups
+  (deferred-tru "Key to retrieve the JWT user's groups")
+  :default "groups")
+
+(defsetting jwt-group-sync
+  (deferred-tru "Enable group membership synchronization with JWT.")
+  :type    :boolean
+  :default false)
+
+(defsetting jwt-group-mappings
+  ;; Should be in the form: {"groupName": [1, 2, 3]} where keys are JWT groups and values are lists of MB groups IDs
+  (deferred-tru "JSON containing JWT to Metabase group mappings.")
+  :type    :json
+  :default {}
+  :setter  (comp (partial setting/set-json! :jwt-group-mappings) validate-group-mappings))
+
+(defn jwt-configured?
+  "Check if JWT is enabled and that the mandatory settings are configured."
+  []
+  (boolean (and (jwt-enabled)
+                (jwt-identity-provider-uri)
+                (jwt-shared-secret))))
+
+(defsetting send-new-sso-user-admin-email?
+  (deferred-tru "Should new email notifications be sent to admins, for all new SSO users?")
+  :type :boolean
+  :default true)
+
+(defsetting other-sso-configured?
+  "Are we using an SSO integration other than LDAP or Google Auth? These integrations use the `/auth/sso` endpoint for
+  authorization rather than the normal login form or Google Auth button."
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (or
+                      (saml-configured?)
+                      (jwt-configured?))))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj
new file mode 100644
index 0000000000000000000000000000000000000000..9e55eb818e45ba1bfc6c8e093fc5e71046425bc5
--- /dev/null
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/sso_utils.clj
@@ -0,0 +1,43 @@
+(ns metabase-enterprise.sso.integrations.sso-utils
+  "Functions shared by the various SSO implementations"
+  (:require [clojure.tools.logging :as log]
+            [metabase-enterprise.sso.integrations.sso-settings :as sso-settings]
+            [metabase.email.messages :as email]
+            [metabase.models.user :refer [User]]
+            [metabase.util :as u]
+            [metabase.util
+             [i18n :refer [trs]]
+             [schema :as su]]
+            [schema.core :as s]
+            [toucan.db :as db])
+  (:import java.util.UUID))
+
+(def ^:private UserAttributes
+  {:first_name       su/NonBlankString
+   :last_name        su/NonBlankString
+   :email            su/Email
+   ;; TODO - we should avoid hardcoding this to make it easier to add new integrations. Maybe look at something like
+   ;; the keys of `(methods sso/sso-get)`
+   :sso_source       (s/enum "saml" "jwt")
+   :login_attributes (s/maybe {s/Any s/Any})})
+
+(s/defn create-new-sso-user!
+  "This function is basically the same thing as the `create-new-google-auth-user` from `metabase.models.user`. We need
+  to refactor the `core_user` table structure and the function used to populate it so that the enterprise product can
+  reuse it"
+  [user :- UserAttributes]
+  (u/prog1 (db/insert! User (merge user {:password (str (UUID/randomUUID))}))
+    (log/info (trs "New SSO user created: {0} ({1})" (:common_name <>) (:email <>)))
+    ;; send an email to everyone including the site admin if that's set
+    (when (sso-settings/send-new-sso-user-admin-email?)
+      (email/send-user-joined-admin-notification-email! <>, :google-auth? true))))
+
+(defn fetch-and-update-login-attributes!
+  "Update the login attributes for the user at `email`. This call is a no-op if the login attributes are the same"
+  [email new-user-attributes]
+  (when-let [{:keys [id login_attributes] :as user} (db/select-one User :%lower.email (u/lower-case-en email))]
+    (if (= login_attributes new-user-attributes)
+      user
+      (do
+        (db/update! User id :login_attributes new-user-attributes)
+        (User id)))))
diff --git a/enterprise/backend/test/metabase_enterprise/audit/pages/common_test.clj b/enterprise/backend/test/metabase_enterprise/audit/pages/common_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c2bf99aecbd01c67869149f458199f44ffa8133d
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/audit/pages/common_test.clj
@@ -0,0 +1,124 @@
+(ns metabase-enterprise.audit.pages.common-test
+  (:require [clojure.test :refer :all]
+            [metabase
+             [db :as mdb]
+             [query-processor :as qp]
+             [test :as mt]]
+            [metabase-enterprise.audit.pages.common :as pages.common]
+            [metabase.public-settings.metastore-test :as metastore-test]))
+
+(defn- run-query
+  [varr & {:as additional-query-params}]
+  (mt/with-test-user :crowberto
+    (metastore-test/with-metastore-token-features #{:audit-app}
+      (qp/process-query (merge {:type :internal
+                                :fn   (let [mta (meta varr)]
+                                        (format "%s/%s" (ns-name (:ns mta)) (:name mta)))}
+                               additional-query-params)))))
+
+(defn- ^:private ^:internal-query-fn legacy-format-query-fn
+  [a1]
+  (let [h2? (= (mdb/db-type) :h2)]
+    {:metadata [[:A {:display_name "A", :base_type :type/DateTime}]
+                [:B {:display_name "B", :base_type :type/Integer}]]
+     :results  (pages.common/query
+                {:union-all [{:select [[a1 :A] [2 :B]]}
+                             {:select [[3 :A] [4 :B]]}]})}))
+
+(defn- ^:private ^:internal-query-fn reducible-format-query-fn
+  [a1]
+  {:metadata [[:A {:display_name "A", :base_type :type/DateTime}]
+              [:B {:display_name "B", :base_type :type/Integer}]]
+   :results  (pages.common/reducible-query
+              {:union-all [{:select [[a1 :A] [2 :B]]}
+                           {:select [[3 :A] [4 :B]]}]})
+   :xform    (map #(update (vec %) 0 inc))})
+
+(deftest transform-results-test
+  (testing "Make sure query function result are transformed to QP results correctly"
+    (metastore-test/with-metastore-token-features #{:audit-app}
+      (doseq [[format-name {:keys [varr expected-rows]}] {"legacy"    {:varr          #'legacy-format-query-fn
+                                                                       :expected-rows [[100 2] [3 4]]}
+                                                          "reducible" {:varr          #'reducible-format-query-fn
+                                                                       :expected-rows [[101 2] [4 4]]}}]
+        (testing (format "format = %s" format-name)
+          (let [results (delay (run-query varr :args [100]))]
+            (testing "cols"
+              (is (= [{:display_name "A", :base_type :type/DateTime, :name "A"}
+                      {:display_name "B", :base_type :type/Integer, :name "B"}]
+                     (mt/cols @results))))
+            (testing "rows"
+              (is (= expected-rows
+                     (mt/rows @results))))))))))
+
+(deftest query-limit-and-offset-test
+  (testing "Make sure params passed in as part of the query map are respected"
+    (metastore-test/with-metastore-token-features #{:audit-app}
+      (doseq [[format-name {:keys [varr expected-rows]}] {"legacy"    {:varr          #'legacy-format-query-fn
+                                                                       :expected-rows [[100 2] [3 4]]}
+                                                          "reducible" {:varr          #'reducible-format-query-fn
+                                                                       :expected-rows [[101 2] [4 4]]}}]
+        (testing (format "format = %s" format-name)
+          (testing :limit
+            (is (= [(first expected-rows)]
+                   (mt/rows (run-query varr :args [100], :limit 1)))))
+          (testing :offset
+            (is (= [(second expected-rows)]
+                   (mt/rows (run-query varr :args [100], :offset 1))))))))))
+
+(deftest CTES->subselects-test
+  (testing "FROM substitution"
+    (is (= {:from [[{:from [[:view_log :vl]]} :mp]]}
+           (#'pages.common/CTEs->subselects
+            {:with      [[:most_popular {:from [[:view_log :vl]]}]]
+             :from      [[:most_popular :mp]]}))))
+
+  (testing "JOIN substitution"
+    (is (= {:left-join [[{:from [[:query_execution :qe]]} :qe_count] [:= :qe_count.executor_id :u.id]]}
+           (#'pages.common/CTEs->subselects
+            {:with      [[:qe_count {:from [[:query_execution :qe]]}]]
+             :left-join [:qe_count [:= :qe_count.executor_id :u.id]]}))))
+
+  (testing "IN subselect substitution"
+    (is (= {:from [[{:from  [[:report_dashboardcard :dc]]
+                     :where [:in :d.id {:from [[{:from [[:view_log :vl]]} :most_popular]]}]} :dash_avg_running_time]]}
+           (#'pages.common/CTEs->subselects
+            {:with [[:most_popular {:from [[:view_log :vl]]}]
+                    [:dash_avg_running_time {:from  [[:report_dashboardcard :dc]]
+                                             :where [:in :d.id {:from [:most_popular]}]}]]
+             :from [:dash_avg_running_time]}))))
+
+  (testing "putting it all together"
+    (is (= {:select    [:mp.dashboard_id]
+            :from      [[{:select    [[:d.id :dashboard_id]]
+                          :from      [[:view_log :vl]]
+                          :left-join [[:report_dashboard :d] [:= :vl.model_id :d.id]]} :mp]]
+            :left-join [[{:select    [[:d.id :dashboard_id]]
+                          :from      [[:report_dashboardcard :dc]]
+                          :left-join [[{:select [:qe.card_id]
+                                        :from   [[:query_execution :qe]]} :rt]
+                                      [:= :dc.card_id :rt.card_id]
+
+                                      [:report_dashboard :d]
+                                      [:= :dc.dashboard_id :d.id]]
+                          :where     [:in :d.id {:select [:dashboard_id]
+                                                 :from   [[{:select    [[:d.id :dashboard_id]]
+                                                            :from      [[:view_log :vl]]
+                                                            :left-join [[:report_dashboard :d]
+                                                                        [:= :vl.model_id :d.id]]} :most_popular]]}]} :rt]
+                        [:= :mp.dashboard_id :rt.dashboard_id]]}
+           (#'pages.common/CTEs->subselects
+            {:with      [[:most_popular {:select    [[:d.id :dashboard_id]]
+                                         :from      [[:view_log :vl]]
+                                         :left-join [[:report_dashboard :d] [:= :vl.model_id :d.id]]}]
+                         [:card_running_time {:select [:qe.card_id]
+                                              :from   [[:query_execution :qe]]}]
+                         [:dash_avg_running_time {:select    [[:d.id :dashboard_id] ]
+                                                  :from      [[:report_dashboardcard :dc]]
+                                                  :left-join [[:card_running_time :rt] [:= :dc.card_id :rt.card_id]
+                                                              [:report_dashboard :d]   [:= :dc.dashboard_id :d.id]]
+                                                  :where     [:in :d.id {:select [:dashboard_id]
+                                                                         :from   [:most_popular]}]}]]
+             :select    [:mp.dashboard_id]
+             :from      [[:most_popular :mp]]
+             :left-join [[:dash_avg_running_time :rt] [:= :mp.dashboard_id :rt.dashboard_id]]})))))
diff --git a/enterprise/backend/test/metabase_enterprise/audit/pages_test.clj b/enterprise/backend/test/metabase_enterprise/audit/pages_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..a737348bd1ff3fe913d71bf45e2e44f11d83f519
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/audit/pages_test.clj
@@ -0,0 +1,93 @@
+(ns metabase-enterprise.audit.pages-test
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
+            [clojure.java.classpath :as classpath]
+            [clojure.tools.namespace.find :as ns-find]
+            [metabase
+             [models :refer [Card Dashboard DashboardCard Database Table]]
+             [query-processor :as qp]
+             [test :as mt]
+             [util :as u]]
+            [metabase.plugins.classloader :as classloader]
+            [metabase.public-settings.metastore-test :as metastore-test]
+            [metabase.query-processor.util :as qp-util]
+            [metabase.test.fixtures :as fixtures]
+            [ring.util.codec :as codec]
+            [schema.core :as s]))
+
+(use-fixtures :once (fixtures/initialize :db))
+
+(deftest preconditions-test
+  (classloader/require 'metabase-enterprise.audit.pages.dashboards)
+  (testing "the query should exist"
+    (is (some? (resolve (symbol "metabase-enterprise.audit.pages.dashboards/most-popular-with-avg-speed")))))
+
+  (testing "test that a query will fail if not ran by an admin"
+    (metastore-test/with-metastore-token-features #{:audit-app}
+      (is (= {:status "failed", :error "You don't have permissions to do that."}
+             (-> (mt/user-http-request :lucky :post 202 "dataset"
+                                       {:type :internal
+                                        :fn   "metabase-enterprise.audit.pages.dashboards/most-popular-with-avg-speed"})
+                 (select-keys [:status :error]))))))
+
+  (testing "ok, now try to run it. Should fail because we don't have audit-app enabled"
+    (metastore-test/with-metastore-token-features nil
+      (is (= {:status "failed", :error "Audit App queries are not enabled on this instance."}
+             (-> (mt/user-http-request :crowberto :post 202 "dataset"
+                                       {:type :internal
+                                        :fn   "metabase-enterprise.audit.pages.dashboards/most-popular-with-avg-speed"})
+                 (select-keys [:status :error])))))))
+
+(defn- all-queries []
+  (for [ns-symb  (ns-find/find-namespaces (classpath/system-classpath))
+        :when    (and (str/starts-with? (name ns-symb) "metabase-enterprise.audit.pages")
+                      (not (str/ends-with? (name ns-symb) "-test")))
+        [_ varr] (do (classloader/require ns-symb)
+                     (ns-interns ns-symb))
+        :when    (:internal-query-fn (meta varr))]
+    varr))
+
+(defn- varr->query [varr {:keys [database table card dash]}]
+  (let [mta     (meta varr)
+        fn-str  (str (ns-name (:ns mta)) "/" (:name mta))
+        arglist (mapv keyword (first (:arglists mta)))]
+    {:type :internal
+     :fn   fn-str
+     :args (for [arg arglist]
+             (case arg
+               :datetime-unit "day"
+               :dashboard-id  (u/get-id dash)
+               :card-id       (u/get-id card)
+               :user-id       (mt/user->id :crowberto)
+               :database-id   (u/get-id database)
+               :table-id      (u/get-id table)
+               :model         "card"
+               :query-hash    (codec/base64-encode (qp-util/query-hash {:database 1, :type :native}))))}))
+
+(defn- test-varr
+  [varr objects]
+  (testing (format "%s %s:%d" varr (ns-name (:ns (meta varr))) (:line (meta varr)))
+    (let [query (varr->query varr objects)]
+      (testing (format "\nquery =\n%s" (u/pprint-to-str query))
+        (is (schema= {:status (s/eq :completed)
+                      s/Keyword s/Any}
+                     (qp/process-query query)))))))
+
+(defn- do-with-temp-objects [f]
+  (mt/with-temp* [Database      [database]
+                  Table         [table {:db_id (u/get-id database)}]
+                  Card          [card {:table_id (u/get-id table), :database_id (u/get-id database)}]
+                  Dashboard     [dash]
+                  DashboardCard [_ {:card_id (u/get-id card), :dashboard_id (u/get-id dash)}]]
+    (f {:database database, :table table, :card card, :dash dash})))
+
+(defmacro ^:private with-temp-objects [[objects-binding] & body]
+  `(do-with-temp-objects (fn [~objects-binding] ~@body)))
+
+(deftest all-queries-test
+  (mt/with-test-user :crowberto
+     (with-temp-objects [objects]
+       (metastore-test/with-metastore-token-features #{:audit-app}
+         (doseq [varr (all-queries)]
+           (test-varr varr objects))))))
diff --git a/enterprise/backend/test/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries_test.clj b/enterprise/backend/test/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..1abc013db779e3f9dcda56cd189a6e2803570527
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/audit/query_processor/middleware/handle_audit_queries_test.clj
@@ -0,0 +1,47 @@
+(ns metabase-enterprise.audit.query-processor.middleware.handle-audit-queries-test
+  "Additional tests for this namespace can be found in `metabase-enterprise.audit.pages-test`."
+  (:require [clojure.test :refer :all]
+            [metabase
+             [query-processor :as qp]
+             [test :as mt]]
+            [metabase.public-settings.metastore-test :as metastore-test]))
+
+(defn- run-query
+  [varr & {:as additional-query-params}]
+  (mt/with-test-user :crowberto
+    (metastore-test/with-metastore-token-features #{:audit-app}
+      (qp/process-query (merge {:type :internal
+                                :fn   (let [mta (meta varr)]
+                                        (format "%s/%s" (ns-name (:ns mta)) (:name mta)))}
+                               additional-query-params)))))
+
+(defn- ^:private ^:internal-query-fn legacy-format-query-fn
+  [a1]
+  {:metadata [[:a {:display_name "A", :base_type :type/DateTime}]
+              [:b {:display_name "B", :base_type :type/Integer}]]
+   :results  [{:a a1, :b 2}
+              {:a 3, :b 5}]})
+
+(defn- ^:private ^:internal-query-fn reducible-format-query-fn
+  [a1]
+  {:metadata [[:a {:display_name "A", :base_type :type/DateTime}]
+              [:b {:display_name "B", :base_type :type/Integer}]]
+   :results  (constantly [[a1 2]
+                          [3 5]])
+   :xform    (map #(update (vec %) 0 inc))})
+
+(deftest transform-results-test
+  (testing "Make sure query function result are transformed to QP results correctly"
+    (doseq [[format-name {:keys [varr expected-rows]}] {"legacy"    {:varr          #'legacy-format-query-fn
+                                                                     :expected-rows [[100 2] [3 5]]}
+                                                        "reducible" {:varr          #'reducible-format-query-fn
+                                                                     :expected-rows [[101 2] [4 5]]}}]
+      (testing (format "format = %s" format-name)
+        (let [results (delay (run-query varr :args [100]))]
+          (testing "cols"
+            (is (= [{:display_name "A", :base_type :type/DateTime, :name "a"}
+                    {:display_name "B", :base_type :type/Integer, :name "b"}]
+                   (mt/cols @results))))
+          (testing "rows"
+            (is (= expected-rows
+                   (mt/rows @results)))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/api/collection_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/api/collection_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fa6aef85898183dfc41aebf7f253e8e31f355581
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/enhancements/api/collection_test.clj
@@ -0,0 +1,34 @@
+(ns metabase-enterprise.enhancements.api.collection-test
+  (:require [clojure.test :refer :all]
+            [metabase
+             [models :refer [NativeQuerySnippet]]
+             [test :as mt]]
+            [metabase.models
+             [collection :as collection]
+             [permissions :as perms]
+             [permissions-group :as group]]
+            [metabase.public-settings.metastore-test :as metastore-test]))
+
+(deftest ee-disabled-snippets-graph-test
+  (testing "GET /api/collection/root/items?namespace=snippets"
+    (mt/with-non-admin-groups-no-root-collection-for-namespace-perms "snippets"
+      (mt/with-temp NativeQuerySnippet [snippet]
+        (letfn [(can-see-snippet? []
+                  (let [response ((mt/user->client :rasta) :get "collection/root/items?namespace=snippets")]
+                    (boolean (some (fn [a-snippet]
+                                     (= (:id snippet) (:id a-snippet)))
+                                   response))))]
+          (testing "\nIf we have a valid EE token, we should only see Snippets in the Root Collection with valid perms"
+            (metastore-test/with-metastore-token-features #{:enhancements}
+              (is (= false
+                     (can-see-snippet?)))
+              (perms/grant-collection-read-permissions! (group/all-users) (assoc collection/root-collection :namespace "snippets"))
+              (is (= true
+                     (can-see-snippet?)))))
+          (testing "\nIf we do not have a valid EE token, all Snippets should come back from the graph regardless of our perms"
+            (metastore-test/with-metastore-token-features #{}
+              (is (= true
+                     (can-see-snippet?)))
+              (perms/revoke-collection-permissions! (group/all-users) (assoc collection/root-collection :namespace "snippets"))
+              (is (= true
+                     (can-see-snippet?))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/api/native_query_snippet_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/api/native_query_snippet_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..ba4e4bd51779f0f380fbb997cd3407edd1afd418
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/enhancements/api/native_query_snippet_test.clj
@@ -0,0 +1,138 @@
+(ns metabase-enterprise.enhancements.api.native-query-snippet-test
+  (:require [clojure.test :refer :all]
+            [metabase
+             [models :refer [Collection NativeQuerySnippet]]
+             [test :as mt]
+             [util :as u]]
+            [metabase.models
+             [collection :as collection]
+             [permissions :as perms]
+             [permissions-group :as group]]
+            [metabase.public-settings.metastore-test :as metastore-test]
+            [toucan.db :as db]))
+
+(def ^:private root-collection (assoc collection/root-collection :name "Root Collection", :namespace "snippets"))
+
+(defn- test-perms
+  "Test whether we have permissions to see/edit/etc. a Snippet by calling `(has-perms? snippet)`. `required-perms` is
+  the permissions we *should* need, either `:read` or `:write`."
+  [required-perms has-perms?]
+  (mt/with-non-admin-groups-no-root-collection-for-namespace-perms "snippets"
+    (mt/with-temp Collection [normal-collection {:name "Normal Collection", :namespace "snippets"}]
+      ;; run tests with both a normal Collection and the Root Collection
+      (doseq [{collection-name :name, :as collection} [normal-collection root-collection]]
+        (testing (format "\nSnippet in %s" collection-name)
+          (mt/with-temp NativeQuerySnippet [snippet {:collection_id (:id collection)}]
+            (testing "\nShould be allowed regardless if EE features aren't enabled"
+              (metastore-test/with-metastore-token-features #{}
+                (is (= true
+                       (has-perms? snippet))
+                    "allowed?")))
+            (testing "\nWith EE features enabled"
+              (metastore-test/with-metastore-token-features #{:enhancements}
+                (testing (format "\nShould not be allowed with no perms for %s" collection-name)
+                  (is (= false
+                         (has-perms? snippet))
+                      "allowed?"))
+                (perms/grant-collection-read-permissions! (group/all-users) collection)
+                (testing (format "\nShould %s allowed if we have read perms for %s"
+                                 (case required-perms :read "be" :write "NOT be")
+                                 collection-name)
+                  (is (= (case required-perms
+                           :read  true
+                           :write false)
+                         (has-perms? snippet))
+                      "allowed?"))
+                (perms/grant-collection-readwrite-permissions! (group/all-users) collection)
+                (testing (format "\nShould be allowed if we have write perms for %s" collection-name)
+                  (is (= true
+                         (has-perms? snippet))
+                      "allowed?"))))))))))
+
+(deftest list-test
+  (testing "GET /api/native-query-snippet"
+    (testing "\nShould only see Snippet if you have parent Collection perms"
+      (test-perms
+       :read
+       (fn [snippet]
+         (boolean
+          (some
+           (fn [a-snippet]
+             (= (u/get-id a-snippet) (u/get-id snippet)))
+           ((mt/user->client :rasta) :get "native-query-snippet"))))))))
+
+(deftest fetch-test
+  (testing "GET /api/native-query-snippet/:id"
+    (testing "\nShould only be able to fetch Snippet if you have parent Collection perms"
+      (test-perms
+       :read
+       (fn [snippet]
+         (let [response ((mt/user->client :rasta) :get (format "native-query-snippet/%d" (u/get-id snippet)))]
+           (not= response "You don't have permissions to do that.")))))))
+
+(deftest create-test
+  (testing "POST /api/native-query-snippet"
+    (testing "\nShould require parent Collection perms to create a new Snippet in that Collection"
+      (test-perms
+       :write
+       (fn [snippet]
+         ;; try creating a copy of the Snippet, but with a different name and with `:id` removed
+         (let [snippet-name       (mt/random-name)
+               snippet-properties (-> snippet (assoc :name snippet-name) (dissoc :id))]
+           (try
+             (let [response ((mt/user->client :rasta) :post "native-query-snippet" snippet-properties)]
+               (not= response "You don't have permissions to do that."))
+             (finally
+               (db/delete! NativeQuerySnippet :name snippet-name)))))))))
+
+(deftest edit-test
+  (testing "PUT /api/native-query-snippet/:id"
+    (testing "\nShould require parent Collection perms to edit a Snippet"
+      (test-perms
+       :write
+       (fn [snippet]
+         (let [response ((mt/user->client :rasta) :put (format "native-query-snippet/%d" (u/get-id snippet)) {:name (mt/random-name)})]
+           (not= response "You don't have permissions to do that.")))))))
+
+(deftest move-perms-test
+  (testing "PUT /api/native-query-snippet/:id"
+    (testing "\nPerms for moving a Snippet"
+      (mt/with-non-admin-groups-no-root-collection-for-namespace-perms "snippets"
+        (mt/with-temp* [Collection [source {:name "Current Parent Collection", :namespace "snippets"}]
+                        Collection [dest   {:name "New Parent Collection", :namespace "snippets"}]]
+          (doseq [source-collection [source root-collection]]
+            (mt/with-temp NativeQuerySnippet [snippet {:collection_id (:id source-collection)}]
+              (doseq [dest-collection [dest root-collection]]
+                (letfn [(has-perms? []
+                          ;; make sure the Snippet is back in the original Collection if it was changed
+                          (db/update! NativeQuerySnippet (:id snippet) :collection_id (:id source-collection))
+                          (let [response ((mt/user->client :rasta) :put (format "native-query-snippet/%d" (:id snippet))
+                                          {:collection_id (:id dest-collection)})]
+                            (cond
+                              (= response "You don't have permissions to do that.")                     false
+                              (and (map? response) (= (:collection_id response) (:id dest-collection))) true
+                              :else                                                                     response)))]
+                  (when-not (= source-collection dest-collection)
+                    (testing (format "\nMove from %s -> %s should need write ('curate') perms for both" (:name source-collection) (:name dest-collection))
+                      (testing "\nShould be allowed if EE perms aren't enabled"
+                        (metastore-test/with-metastore-token-features #{}
+                          (is (= true
+                                 (has-perms?)))))
+                      (metastore-test/with-metastore-token-features #{:enhancements}
+                        (doseq [c [source-collection dest-collection]]
+                          (testing (format "\nPerms for only %s should fail" (:name c))
+                            (try
+                              (perms/grant-collection-readwrite-permissions! (group/all-users) c)
+                              (is (= false
+                                     (has-perms?)))
+                              (finally
+                                (perms/revoke-collection-permissions! (group/all-users) c)))))
+                        (testing "\nShould succeed with both"
+                          (try
+                            (doseq [c [source-collection dest-collection]]
+                              (perms/grant-collection-readwrite-permissions! (group/all-users) c))
+                            (is (= true
+                                   (has-perms?)))
+                            (finally
+                              (doseq [c [source-collection dest-collection]]
+                                (perms/revoke-collection-permissions! (group/all-users) c)))))))))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/ee_strategy_impl_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/ee_strategy_impl_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..32002ce686e667d0b82b02f92fe6ef4274c38cdd
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/enhancements/ee_strategy_impl_test.clj
@@ -0,0 +1,85 @@
+(ns metabase-enterprise.enhancements.ee-strategy-impl-test
+  (:require [clojure.test :refer :all]
+            [metabase-enterprise.enhancements.ee-strategy-impl :as ee-strategy-impl]
+            [metabase.public-settings.metastore :as metastore]
+            [pretty.core :refer [PrettyPrintable]]))
+
+(defprotocol ^:private MyProtocol
+  (m1 [this] [this x])
+  (m2 [this x y]))
+
+(deftest resolve-protocol-test
+  (binding [*ns* (the-ns 'metabase-enterprise.enhancements.ee-strategy-impl-test)]
+    (doseq [protocol-symb ['MyProtocol
+                           `MyProtocol
+                           (symbol (.getCanonicalName ^Class (:on-interface MyProtocol)))]]
+      (testing (format "Protocol symbol = ^%s %s" (.getCanonicalName (class protocol-symb)) (pr-str protocol-symb))
+        (is (= MyProtocol
+               (#'ee-strategy-impl/resolve-protocol protocol-symb)))))))
+
+(deftest generate-method-impl-test
+  (is (= '((m1 [_]
+               (metabase-enterprise.enhancements.ee-strategy-impl/invoke-ee-when-enabled
+                metabase-enterprise.enhancements.ee-strategy-impl-test/m1
+                ee oss))
+           (m1 [_ a]
+               (metabase-enterprise.enhancements.ee-strategy-impl/invoke-ee-when-enabled
+                metabase-enterprise.enhancements.ee-strategy-impl-test/m1
+                ee oss
+                a)))
+         (#'ee-strategy-impl/generate-method-impl 'ee 'oss
+                                               {:var #'MyProtocol}
+                                               {:name     'm1
+                                                :arglists '([this] [this x])}))))
+
+(deftest generate-protocol-impl-test
+  (binding [*ns* (the-ns 'metabase-enterprise.enhancements.ee-strategy-impl-test)]
+    (doseq [protocol-symb ['MyProtocol
+                           `MyProtocol
+                           (symbol (.getCanonicalName ^Class (:on-interface MyProtocol)))]]
+      (testing (format "Protocol symbol = ^%s %s" (.getCanonicalName (class protocol-symb)) (pr-str protocol-symb))
+        (is (= '(metabase_enterprise.enhancements.ee_strategy_impl_test.MyProtocol
+                 (m1 [_]
+                     (metabase-enterprise.enhancements.ee-strategy-impl/invoke-ee-when-enabled
+                      metabase-enterprise.enhancements.ee-strategy-impl-test/m1
+                      ee oss))
+                 (m1 [_ a]
+                     (metabase-enterprise.enhancements.ee-strategy-impl/invoke-ee-when-enabled
+                      metabase-enterprise.enhancements.ee-strategy-impl-test/m1
+                      ee oss
+                      a))
+                 (m2 [_ a b]
+                     (metabase-enterprise.enhancements.ee-strategy-impl/invoke-ee-when-enabled
+                      metabase-enterprise.enhancements.ee-strategy-impl-test/m2
+                      ee oss
+                      a b)))
+               (#'ee-strategy-impl/generate-protocol-impl 'ee 'oss protocol-symb)))))))
+
+(deftest e2e-test
+  (let [ee   (reify
+               PrettyPrintable
+               (pretty [_] '(ee))
+               MyProtocol
+               (m2 [_ x y]
+                 (+ x y)))
+        oss  (reify
+               PrettyPrintable
+               (pretty [_] '(oss))
+               MyProtocol
+               (m2 [_ x y]
+                 (- x y)))
+        impl (ee-strategy-impl/reify-ee-strategy-impl ee oss MyProtocol)]
+    (testing "sanity check"
+      (is (= 3
+             (m2 ee 1 2)))
+      (is (= -1
+             (m2 oss 1 2))))
+    (with-redefs [metastore/enable-enhancements? (constantly false)]
+      (is (= -1
+             (m2 impl 1 2))))
+    (with-redefs [metastore/enable-enhancements? (constantly true)]
+      (is (= 3
+             (m2 impl 1 2))))
+    (testing "Should pretty print"
+      (is (= "(metabase-enterprise.enhancements.ee-strategy-impl/reify-ee-strategy-impl (ee) (oss))"
+             (pr-str impl))))))
diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..289586b6dd87b2d05fc90a614fedf367b3d80bcf
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj
@@ -0,0 +1,174 @@
+(ns metabase-enterprise.enhancements.integrations.ldap-test
+  (:require [clojure.test :refer :all]
+            [metabase.integrations.ldap :as ldap]
+            [metabase.models.user :as user :refer [User]]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.test :as mt]
+            [metabase.test.integrations.ldap :as ldap.test]
+            [metabase.util.schema :as su]
+            [schema.core :as s]
+            [toucan.db :as db]))
+
+(deftest find-test
+  (with-redefs [metastore/enable-enhancements? (constantly true)]
+    (ldap.test/with-ldap-server
+      (testing "find by username"
+        (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
+                :first-name "John"
+                :last-name  "Smith"
+                :email      "John.Smith@metabase.com"
+                :attributes {:uid       "jsmith1"
+                             :mail      "John.Smith@metabase.com"
+                             :givenname "John"
+                             :sn        "Smith"
+                             :cn        "John Smith"}
+                :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
+               (ldap/find-user "jsmith1"))))
+
+      (testing "find by email"
+        (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
+                :first-name "John"
+                :last-name  "Smith"
+                :email      "John.Smith@metabase.com"
+                :attributes {:uid       "jsmith1"
+                             :mail      "John.Smith@metabase.com"
+                             :givenname "John"
+                             :sn        "Smith"
+                             :cn        "John Smith"}
+                :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
+               (ldap/find-user "John.Smith@metabase.com"))))
+
+      (testing "find by email, no groups"
+        (is (= {:dn         "cn=Fred Taylor,ou=People,dc=metabase,dc=com"
+                :first-name "Fred"
+                :last-name  "Taylor"
+                :email      "fred.taylor@metabase.com"
+                :attributes {:uid       "ftaylor300"
+                             :mail      "fred.taylor@metabase.com"
+                             :cn        "Fred Taylor"
+                             :givenname "Fred"
+                             :sn        "Taylor"}
+                :groups     []}
+               (ldap/find-user "fred.taylor@metabase.com")))))))
+
+(deftest attribute-sync-test
+  (with-redefs [metastore/enable-enhancements? (constantly true)]
+    (ldap.test/with-ldap-server
+      (testing "find by email/username should return other attributes as well"
+        (is (= {:dn         "cn=Lucky Pigeon,ou=Birds,dc=metabase,dc=com"
+                :first-name "Lucky"
+                :last-name  "Pigeon"
+                :email      "lucky@metabase.com"
+                :attributes {:uid       "lucky"
+                             :mail      "lucky@metabase.com"
+                             :title     "King Pigeon"
+                             :givenname "Lucky"
+                             :sn        "Pigeon"
+                             :cn        "Lucky Pigeon"}
+                :groups     []}
+               (ldap/find-user "lucky"))))
+
+      (testing "ignored attributes should not be returned"
+        (mt/with-temporary-setting-values [ldap-sync-user-attributes-blacklist
+                                           (cons "title" (ldap/ldap-sync-user-attributes-blacklist))]
+          (is (= {:dn         "cn=Lucky Pigeon,ou=Birds,dc=metabase,dc=com"
+                  :first-name "Lucky"
+                  :last-name  "Pigeon"
+                  :email      "lucky@metabase.com"
+                  :attributes {:uid       "lucky"
+                               :mail      "lucky@metabase.com"
+                               :givenname "Lucky"
+                               :sn        "Pigeon"
+                               :cn        "Lucky Pigeon"}
+                  :groups     []}
+                 (ldap/find-user "lucky")))))
+
+      (testing "if attribute sync is disabled, no attributes should come back at all"
+        (mt/with-temporary-setting-values [ldap-sync-user-attributes false]
+          (is (= {:dn         "cn=Lucky Pigeon,ou=Birds,dc=metabase,dc=com"
+                  :first-name "Lucky"
+                  :last-name  "Pigeon"
+                  :email      "lucky@metabase.com"
+                  :attributes nil
+                  :groups     []}
+                 (ldap/find-user "lucky")))))
+
+      (testing "when creating a new user, user attributes should get synced"
+        (try
+          (ldap/fetch-or-create-user! (ldap/find-user "jsmith1"))
+          (is (= {:first_name       "John"
+                  :last_name        "Smith"
+                  :email            "john.smith@metabase.com"
+                  :login_attributes {"uid"       "jsmith1"
+                                     "mail"      "John.Smith@metabase.com"
+                                     "givenname" "John"
+                                     "sn"        "Smith"
+                                     "cn"        "John Smith"}
+                  :common_name      "John Smith"}
+                 (into {} (db/select-one [User :first_name :last_name :email :login_attributes]
+                            :email "john.smith@metabase.com"))))
+          (finally
+            (db/delete! User :%lower.email "john.smith@metabase.com"))))
+
+      (testing "when creating a new user and attribute sync is disabled, attributes should not be synced"
+        (mt/with-temporary-setting-values [ldap-sync-user-attributes false]
+          (try
+            (ldap/fetch-or-create-user! (ldap/find-user "jsmith1"))
+            (is (= {:first_name       "John"
+                    :last_name        "Smith"
+                    :email            "john.smith@metabase.com"
+                    :login_attributes nil
+                    :common_name      "John Smith"}
+                   (into {} (db/select-one [User :first_name :last_name :email :login_attributes]
+                              :email "john.smith@metabase.com"))))
+            (finally
+              (db/delete! User :%lower.email "john.smith@metabase.com"))))))))
+
+(deftest update-attributes-on-login-test
+  (with-redefs [metastore/enable-enhancements? (constantly true)]
+    (ldap.test/with-ldap-server
+      (testing "Existing user's attributes are updated on fetch"
+        (try
+          (let [user-info (ldap/find-user "jsmith1")]
+            (testing "First let a user get created for John Smith"
+              (is (schema= {:email    (s/eq "john.smith@metabase.com")
+                            s/Keyword s/Any}
+                           (ldap/fetch-or-create-user! user-info))))
+            (testing "Call fetch-or-create-user! again to trigger update"
+              (is (schema= {:id su/IntGreaterThanZero,  s/Keyword s/Any}
+                           (ldap/fetch-or-create-user! (assoc-in user-info [:attributes :unladenspeed] 100)))))
+            (is (= {:first_name       "John"
+                    :last_name        "Smith"
+                    :common_name      "John Smith"
+                    :email            "john.smith@metabase.com"
+                    :login_attributes {"uid"          "jsmith1"
+                                       "mail"         "John.Smith@metabase.com"
+                                       "givenname"    "John"
+                                       "sn"           "Smith"
+                                       "cn"           "John Smith"
+                                       "unladenspeed" 100}}
+                   (into {} (db/select-one [User :first_name :last_name :email :login_attributes]
+                              :email "john.smith@metabase.com")))))
+          (finally
+            (db/delete! User :%lower.email "john.smith@metabase.com"))))
+
+      (testing "Existing user's attributes are not updated on fetch, when attribute sync is disabled"
+        (try
+          (mt/with-temporary-setting-values [ldap-sync-user-attributes false]
+            (let [user-info (ldap/find-user "jsmith1")]
+              (testing "First let a user get created for John Smith"
+                (is (schema= {:email    (s/eq "john.smith@metabase.com")
+                              s/Keyword s/Any}
+                             (ldap/fetch-or-create-user! user-info))))
+              (testing "Call fetch-or-create-user! again to trigger update"
+                (is (schema= {:id su/IntGreaterThanZero,  s/Keyword s/Any}
+                             (ldap/fetch-or-create-user! (assoc-in user-info [:attributes :unladenspeed] 100)))))
+              (is (= {:first_name       "John"
+                      :last_name        "Smith"
+                      :common_name      "John Smith"
+                      :email            "john.smith@metabase.com"
+                      :login_attributes nil}
+                     (into {} (db/select-one [User :first_name :last_name :email :login_attributes]
+                                :email "john.smith@metabase.com"))))))
+          (finally
+            (db/delete! User :%lower.email "john.smith@metabase.com")))))))
diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/models/native_query_snippet/permissions_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/models/native_query_snippet/permissions_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e17db925f5062aa2a393e1ec3a3f5d912b56ab7b
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/enhancements/models/native_query_snippet/permissions_test.clj
@@ -0,0 +1,68 @@
+(ns metabase-enterprise.enhancements.models.native-query-snippet.permissions-test
+  (:require [clojure.test :refer :all]
+            [metabase
+             [models :refer [Collection NativeQuerySnippet]]
+             [test :as mt]]
+            [metabase.models
+             [collection :as collection]
+             [interface :as i]
+             [permissions :as perms]
+             [permissions-group :as group]]
+            [metabase.public-settings.metastore-test :as metastore-test]))
+
+(def ^:private root-collection (assoc collection/root-collection :name "Root Collection", :namespace "snippets"))
+
+(defn- test-perms [& {:keys [has-perms-for-obj? has-perms-for-id? grant-collection-perms!]}]
+  (letfn [(test-perms* [expected]
+            (mt/with-test-user :rasta
+              (when has-perms-for-obj?
+                (testing "has perms for object?"
+                  (is (= expected
+                         (has-perms-for-obj?)))))
+              (when has-perms-for-id?
+                (testing "has perms for model + ID?"
+                  (is (= expected
+                         (has-perms-for-id?)))))))]
+    (testing "should be allowed if EE perms aren't enabled"
+      (metastore-test/with-metastore-token-features #{}
+        (test-perms* true)))
+    (metastore-test/with-metastore-token-features #{:enhancements}
+      (testing "should NOT be allowed if EE perms are enabled and you do not have perms"
+        (test-perms* false))
+      (testing "should be allowed if you have perms"
+        (grant-collection-perms!)
+        (test-perms* true)))))
+
+(defn- test-with-root-collection-and-collection [f]
+  (mt/with-non-admin-groups-no-root-collection-for-namespace-perms "snippets"
+    (mt/with-temp Collection [collection {:name "Parent Collection", :namespace "snippets"}]
+      (doseq [coll [root-collection collection]]
+        (mt/with-temp NativeQuerySnippet [snippet {:collection_id (:id coll)}]
+          (testing (format "in %s\n" (:name coll))
+            (f coll snippet)))))))
+
+(deftest read-perms-test
+  (testing "read a Snippet"
+    (test-with-root-collection-and-collection
+     (fn [coll snippet]
+       (test-perms
+        :has-perms-for-obj?      #(i/can-read? snippet)
+        :has-perms-for-id?       #(i/can-read? NativeQuerySnippet (:id snippet))
+        :grant-collection-perms! #(perms/grant-collection-read-permissions! (group/all-users) coll))))))
+
+(deftest create-perms-test
+  (testing "create a Snippet"
+    (test-with-root-collection-and-collection
+     (fn [coll snippet]
+       (test-perms
+        :has-perms-for-object?   #(i/can-create? NativeQuerySnippet (dissoc snippet :id))
+        :grant-collection-perms! #(perms/grant-collection-readwrite-permissions! (group/all-users) coll))))))
+
+(deftest update-perms-test
+  (testing "update a Snippet"
+    (test-with-root-collection-and-collection
+     (fn [coll snippet]
+       (test-perms
+        :has-perms-for-obj?      #(i/can-write? snippet)
+        :has-perms-for-id?       #(i/can-write? NativeQuerySnippet (:id snippet))
+        :grant-collection-perms! #(perms/grant-collection-readwrite-permissions! (group/all-users) coll))))))
diff --git a/enterprise/backend/test/metabase_enterprise/public_settings_test.clj b/enterprise/backend/test/metabase_enterprise/public_settings_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..e38764b69268ca5f63bd24fbe7e6468d6f5069f0
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/public_settings_test.clj
@@ -0,0 +1,19 @@
+(ns metabase-enterprise.public-settings-test
+  (:require [clojure.test :refer :all]
+            [metabase.public-settings :as public-settings]
+            [metabase-enterprise.sso.integrations.sso-settings :as sso-settings]
+            [metabase.api.session :as session]
+            [metabase.test
+             [fixtures :as fixtures]
+             [util :as tu]]))
+
+(use-fixtures :once (fixtures/initialize :db))
+
+(deftest can-turn-off-password-login-with-jwt-enabled
+  (is (= false
+         (tu/with-temporary-setting-values
+           [jwt-enabled               true
+            jwt-identity-provider-uri "example.com"
+            jwt-shared-secret         "0123456789012345678901234567890123456789012345678901234567890123"]
+           (public-settings/enable-password-login false)
+           (public-settings/enable-password-login)))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/card_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/card_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..14c960f7d36d5e8e8cb38a4c9a39b6f5326f7b0e
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/card_test.clj
@@ -0,0 +1,47 @@
+(ns metabase-enterprise.sandbox.api.card-test
+  (:require [clojure.test :refer :all]
+            [metabase
+             [models :refer [Card Collection Database PermissionsGroup PermissionsGroupMembership Table]]
+             [test :as mt]
+             [util :as u]]
+            [metabase.api.card-test :as card-api.test]
+            [metabase.models
+             [permissions :as perms]
+             [permissions-group :as perms-group]]))
+
+(deftest users-with-segmented-perms-test
+  (testing "Users with segmented permissions should be able to save cards"
+    (let [card-name (mt/random-name)]
+      (mt/with-model-cleanup [Card]
+        (mt/with-temp* [Database                   [db]
+                        Collection                 [collection]
+                        Table                      [table {:db_id (u/get-id db)}]
+                        PermissionsGroup           [group]
+                        PermissionsGroupMembership [_ {:user_id (mt/user->id :rasta)
+                                                       :group_id (u/get-id group)}]]
+          (mt/with-db db
+            (perms/revoke-permissions! (perms-group/all-users) db)
+            (perms/grant-permissions! group (perms/table-segmented-query-path table))
+            (perms/grant-collection-readwrite-permissions! group collection)
+            (is (some? ((mt/user->client :rasta) :post 202 "card"
+                        (assoc (card-api.test/card-with-name-and-query card-name (card-api.test/mbql-count-query db table))
+                               :collection_id (u/get-id collection)))))))))
+
+    (testing "Users with segmented permissions should be able to update the query associated to a card"
+      (mt/with-model-cleanup [Card]
+        (mt/with-temp* [Database                   [db]
+                        Collection                 [collection]
+                        Table                      [table {:db_id (u/get-id db)}]
+                        PermissionsGroup           [group]
+                        PermissionsGroupMembership [_ {:user_id (mt/user->id :rasta)
+                                                       :group_id (u/get-id group)}]
+                        Card                       [card {:name "Some Name"
+                                                          :collection_id (u/get-id collection)}]]
+          (mt/with-db db
+            (perms/revoke-permissions! (perms-group/all-users) db)
+            (perms/grant-permissions! group (perms/table-segmented-query-path table))
+            (perms/grant-collection-readwrite-permissions! group collection)
+            (is (= "Another Name"
+                   (:name ((mt/user->client :rasta) :put 202 (str "card/" (u/get-id card))
+                           {:name          "Another Name"
+                            :dataset_query (card-api.test/mbql-count-query db table)}))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/dashboard_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/dashboard_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..645fdcdce2e5c638e8f3b0660b3e2cd173aac4cb
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/dashboard_test.clj
@@ -0,0 +1,21 @@
+(ns metabase-enterprise.sandbox.api.dashboard-test
+  "Tests for special behavior of `/api/metabase/dashboard` endpoints in the Metabase Enterprise Edition."
+  (:require [clojure.test :refer :all]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase
+             [models :refer [Card Dashboard DashboardCard]]
+             [test :as mt]]))
+
+(deftest params-values-test
+  (testing "Don't return `param_values` for fields to which the user only has segmented access."
+    (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+      (mt.tu/with-user-attributes :rasta {:cat 50}
+        (mt/with-temp* [Dashboard     [{dashboard-id :id} {:name "Test Dashboard"}]
+                        Card          [{card-id :id}      {:name "Dashboard Test Card"}]
+                        DashboardCard [{_ :id}            {:dashboard_id       dashboard-id
+                                                           :card_id            card-id
+                                                           :parameter_mappings [{:card_id      card-id
+                                                                                 :parameter_id "foo"
+                                                                                 :target       [:dimension [:field_id (mt/id :venues :name)]]}]}]]
+          (is (= nil
+                 (:param_values ((mt/user->client :rasta) :get 200 (str "dashboard/" dashboard-id))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..9cc3bf6128a5871f1c6c73848d7fc015b50d2464
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/field_test.clj
@@ -0,0 +1,99 @@
+(ns metabase-enterprise.sandbox.api.field-test
+  "Tests for special behavior of `/api/metabase/field` endpoints in the Metabase Enterprise Edition."
+  (:require [clojure.test :refer :all]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase.models
+             [field :refer [Field]]
+             [field-values :as field-values :refer [FieldValues]]]
+            [metabase.test :as mt]
+            [toucan.db :as db]))
+
+(deftest fetch-field-test
+  (testing "GET /api/field/:id"
+    (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+      (mt.tu/with-user-attributes :rasta {:cat 50}
+        (testing "Can I fetch a Field that I don't have read access for if I have segmented table access for it?"
+          (let [result ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :name)))]
+            (is (map? result))
+            (is (= {:name             "NAME"
+                    :display_name     "Name"
+                    :has_field_values "list"}
+                   (select-keys result [:name :display_name :has_field_values])))))))))
+
+(deftest field-values-test
+  (testing "GET /api/field/:id/values"
+    (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+      (mt.tu/with-user-attributes :rasta {:cat 50}
+        (testing (str "When I call the FieldValues API endpoint for a Field that I have segmented table access only "
+                      "for, will I get ad-hoc values?")
+          ;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. So
+          ;; fetching FieldValues for `venue.name` should do an ad-hoc fetch and only return the names of venues in
+          ;; that category.
+          (let [result ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :name) "/values"))]
+            (is (= {:field_id (mt/id :venues :name)
+                    :values   [["Garaje"]
+                               ["Gordo Taqueria"]
+                               ["La Tortilla"]
+                               ["Manuel's Original El Tepeyac Cafe"]
+                               ["Señor Fish"]
+                               ["Tacos Villa Corona"]
+                               ["Taqueria Los Coyotes"]
+                               ["Taqueria San Francisco"]
+                               ["Tito's Tacos"]
+                               ["Yuca's Taqueria"]]}
+                   result))))
+
+        (testing (str "Now in this case recall that the `restricted-column-query` GTAP we're using does *not* include "
+                      "`venues.price` in the results. (Toucan isn't allowed to know the number of dollar signs!) So "
+                      "make sure if we try to fetch the field values instead of seeing `[[1] [2] [3] [4]]` we get no "
+                      "results")
+          (mt/suppress-output
+            (let [result ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :price) "/values"))]
+              (is (= {:field_id (mt/id :venues :price)
+                      :values   []}
+                     result)))))))))
+
+(deftest search-test
+  (testing "GET /api/field/:id/search/:search-id"
+    (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+      (mt.tu/with-user-attributes :rasta {:cat 50}
+        (testing (str "Searching via the query builder needs to use a GTAP when the user has segmented permissions. "
+                      "This tests out a field search on a table with segmented permissions")
+          ;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. So
+          ;; searching whould only include venues in that category
+          (let [url (format "field/%s/search/%s" (mt/id :venues :name) (mt/id :venues :name))]
+            (is (= [["Tacos Villa Corona"     "Tacos Villa Corona"]
+                    ["Taqueria Los Coyotes"   "Taqueria Los Coyotes"]
+                    ["Taqueria San Francisco" "Taqueria San Francisco"]]
+                   ((mt/user->client :rasta) :get 200 url :value "Ta")))))))))
+
+(deftest caching-test
+  (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+    (mt.tu/with-user-attributes :rasta {:cat 50}
+      (let [field (Field (mt/id :venues :name))]
+        ;; Make sure FieldValues are populated
+        (field-values/create-field-values-if-needed! field)
+        ;; Warm up the cache
+        ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :name) "/values"))
+        (testing "Do we use cached values when available?"
+          (with-redefs [field-values/distinct-values (fn [_] (assert false "Should not be called"))]
+            (is (some? (:values ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :name) "/values")))))))
+        (testing "Do we invalidate the cache when FieldValues change"
+          (try
+            (let [;; Updating FieldValues which should invalidate the cache
+                  fv-id          (db/select-one-id FieldValues :field_id (mt/id :venues :name))
+                  old-updated-at (db/select-one-field :updated_at FieldValues :field_id (mt/id :venues :name))
+                  new-values     ["foo" "bar"]]
+              (testing "Sanity check: make sure FieldValues exist"
+                (is (some? fv-id)))
+              (db/update! FieldValues fv-id
+                {:values new-values})
+              (testing "Sanity check: make sure updated_at has been updated"
+                (is (not= (db/select-one-field :updated_at FieldValues :field_id (mt/id :venues :name))
+                          old-updated-at)))
+              (with-redefs [field-values/distinct-values (constantly new-values)]
+                (is (= (map vector new-values)
+                       (:values ((mt/user->client :rasta) :get 200 (str "field/" (mt/id :venues :name) "/values")))))))
+            (finally
+              ;; Put everything back as it was
+              (field-values/create-field-values-if-needed! field))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..12c88341a59050f052b0bb6122306686101f89ef
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj
@@ -0,0 +1,167 @@
+(ns metabase-enterprise.sandbox.api.gtap-test
+  (:require [expectations :refer :all]
+            [metabase.http-client :as http]
+            [metabase.middleware.util :as middleware.u]
+            [metabase.models
+             [card :refer [Card]]
+             [permissions-group :refer [PermissionsGroup]]
+             [table :refer [Table]]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.test.data.users :refer :all]
+            [metabase.test.util :as tu]
+            [toucan.util.test :as tt]))
+
+(defmacro ^:private with-sandboxes-enabled [& body]
+  `(with-redefs [metastore/enable-sandboxes? (constantly true)]
+     ~@body))
+
+;; Must be authenticated to query for gtaps
+(expect (get middleware.u/response-unauthentic :body)
+        (with-sandboxes-enabled
+          (http/client :get 401 "mt/gtap")))
+
+(expect
+  "You don't have permissions to do that."
+  (with-sandboxes-enabled
+    ((user->client :rasta) :get 403 (str "mt/gtap"))))
+
+(def ^:private default-gtap-results
+  {:id                   true
+   :card_id              true
+   :table_id             true
+   :group_id             true
+   :attribute_remappings {:foo 1}})
+
+(defmacro ^:private with-gtap-cleanup
+  "Invokes `body` ensuring any `GroupTableAccessPolicy` created will be removed afterward. Leaving behind a GTAP can
+  case referential integrity failures for any related `Card` that would be cleaned up as part of a `with-temp*` call"
+  [& body]
+  `(with-sandboxes-enabled
+     (tu/with-model-cleanup [GroupTableAccessPolicy]
+       ~@body)))
+
+(defn- gtap-post
+  "`gtap-data` is a map to be POSTed to the GTAP endpoint"
+  [gtap-data]
+  ((user->client :crowberto) :post 200 "mt/gtap" gtap-data))
+
+;; ## POST /api/mt/gtap
+;; Must have a valid token to use GTAPs
+(expect
+  #"sandboxing is not enabled"
+  (tt/with-temp* [Table            [{table-id :id}]
+                  PermissionsGroup [{group-id :id}]
+                  Card             [{card-id :id}]]
+    ((user->client :crowberto) :post 403 "mt/gtap"
+     {:table_id             table-id
+      :group_id             group-id
+      :card_id              card-id
+      :attribute_remappings {"foo" 1}})))
+
+;; Test that we can create a new GTAP
+(expect
+  [default-gtap-results true]
+  (tt/with-temp* [Table            [{table-id :id}]
+                  PermissionsGroup [{group-id :id}]
+                  Card             [{card-id :id}]]
+    (with-gtap-cleanup
+      (let [post-results (gtap-post {:table_id             table-id
+                                     :group_id             group-id
+                                     :card_id              card-id
+                                     :attribute_remappings {"foo" 1}})]
+        [(tu/boolean-ids-and-timestamps post-results)
+         (= post-results ((user->client :crowberto) :get 200 (format "mt/gtap/%s" (:id post-results))))]))))
+
+;; Test that we can create a new GTAP without a card
+(expect
+  [(assoc default-gtap-results :card_id false)
+   true]
+  (tt/with-temp* [Table            [{table-id :id}]
+                  PermissionsGroup [{group-id :id}]]
+    (with-gtap-cleanup
+      (let [post-results (gtap-post {:table_id             table-id
+                                     :group_id             group-id
+                                     :card_id              nil
+                                     :attribute_remappings {"foo" 1}})]
+        [(tu/boolean-ids-and-timestamps post-results)
+         (= post-results ((user->client :crowberto) :get 200 (format "mt/gtap/%s" (:id post-results))))]))))
+
+;; Test that we can delete a GTAP
+(expect
+  [default-gtap-results
+   "Not found."]
+  (tt/with-temp* [Table            [{table-id :id}]
+                  PermissionsGroup [{group-id :id}]
+                  Card             [{card-id :id}]]
+    (with-gtap-cleanup
+      (let [{:keys [id]} (gtap-post {:table_id             table-id
+                                     :group_id             group-id
+                                     :card_id              card-id
+                                     :attribute_remappings {"foo" 1}})]
+        [(tu/boolean-ids-and-timestamps ((user->client :crowberto) :get 200 (format "mt/gtap/%s" id)))
+         (do
+           ((user->client :crowberto) :delete 204 (format "mt/gtap/%s" id))
+           ((user->client :crowberto) :get 404 (format "mt/gtap/%s" id)))]))))
+
+;; ## PUT /api/mt/gtap
+;; Test that we can update only the attribute remappings for a GTAP
+(expect
+  (assoc default-gtap-results :attribute_remappings {:bar 2})
+  (tt/with-temp* [Table                  [{table-id :id}]
+                  PermissionsGroup       [{group-id :id}]
+                  Card                   [{card-id :id}]
+                  GroupTableAccessPolicy [{gtap-id :id} {:table_id             table-id
+                                                         :group_id             group-id
+                                                         :card_id              card-id
+                                                         :attribute_remappings {"foo" 1}}]]
+    (with-sandboxes-enabled
+      (tu/boolean-ids-and-timestamps
+       ((user->client :crowberto) :put 200 (format "mt/gtap/%s" gtap-id)
+        {:attribute_remappings {:bar 2}})))))
+
+;; Test that we can add a card_id via PUT
+(expect
+  default-gtap-results
+  (tt/with-temp* [Table                  [{table-id :id}]
+                  PermissionsGroup       [{group-id :id}]
+                  Card                   [{card-id :id}]
+                  GroupTableAccessPolicy [{gtap-id :id} {:table_id             table-id
+                                                         :group_id             group-id
+                                                         :card_id              nil
+                                                         :attribute_remappings {"foo" 1}}]]
+    (with-sandboxes-enabled
+      (tu/boolean-ids-and-timestamps
+       ((user->client :crowberto) :put 200 (format "mt/gtap/%s" gtap-id)
+        {:card_id card-id})))))
+
+;; Test that we can remove a card_id via PUT
+(expect
+  (assoc default-gtap-results :card_id false)
+  (tt/with-temp* [Table                  [{table-id :id}]
+                  PermissionsGroup       [{group-id :id}]
+                  Card                   [{card-id :id}]
+                  GroupTableAccessPolicy [{gtap-id :id} {:table_id             table-id
+                                                         :group_id             group-id
+                                                         :card_id              card-id
+                                                         :attribute_remappings {"foo" 1}}]]
+    (with-sandboxes-enabled
+      (tu/boolean-ids-and-timestamps
+       ((user->client :crowberto) :put 200 (format "mt/gtap/%s" gtap-id)
+        {:card_id nil})))))
+
+;; Test that we can remove a card_id and change attribute remappings via PUT
+(expect
+  (assoc default-gtap-results :card_id false, :attribute_remappings {:bar 2})
+  (tt/with-temp* [Table                  [{table-id :id}]
+                  PermissionsGroup       [{group-id :id}]
+                  Card                   [{card-id :id}]
+                  GroupTableAccessPolicy [{gtap-id :id} {:table_id             table-id
+                                                         :group_id             group-id
+                                                         :card_id              card-id
+                                                         :attribute_remappings {"foo" 1}}]]
+    (with-sandboxes-enabled
+      (tu/boolean-ids-and-timestamps
+       ((user->client :crowberto) :put 200 (format "mt/gtap/%s" gtap-id)
+        {:card_id              nil
+         :attribute_remappings {:bar 2}})))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/pulse_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/pulse_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3d91c6d698349b6ed81ce777bf5b428f049262e5
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/pulse_test.clj
@@ -0,0 +1,37 @@
+(ns metabase-enterprise.sandbox.api.pulse-test
+  "Tests that would logically be included in `metabase.api.pulse-test` but are separate as they are enterprise only."
+  (:require [clojure.test :refer :all]
+            [metabase
+             [models :refer [Card PermissionsGroup PermissionsGroupMembership]]
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase-enterprise.sandbox.test-util :as sandbox.tu]
+            metabase.integrations.slack))
+
+(comment metabase.integrations.slack/keep-me) ; so the Setting exists
+
+(deftest segmented-users-pulse-test
+  (testing "GET /api/pulse/form_input"
+    (testing (str "Non-segmented users are able to send pulses to any slack channel that the configured instance can "
+                  "see. A segmented user should not be able to send messages to those channels. This tests that a "
+                  "segmented user doesn't see any slack channels.")
+      (mt/with-temp-copy-of-db
+        (mt/with-temp* [Card [{card-id :id :as card} {:name          "magic"
+                                                      :dataset_query {:database (u/get-id (mt/db))
+                                                                      :type     :native
+                                                                      :native   {:query         "SELECT * FROM VENUES WHERE category_id = {{cat}}"
+                                                                                 :template_tags {:cat {:name "cat" :display_name "cat" :type "number" :required true}}}}}]
+                        PermissionsGroup [{group-id :id} {:name "Restricted Venues"}]
+                        PermissionsGroupMembership [_ {:group_id group-id
+                                                       :user_id  (mt/user->id :rasta)}]
+                        GroupTableAccessPolicy [gtap {:group_id             group-id
+                                                      :table_id             (mt/id :venues)
+                                                      :card_id              card-id
+                                                      :attribute_remappings {:cat ["variable" ["template-tag" "cat"]]}}]]
+
+          (sandbox.tu/add-segmented-perms-for-venues-for-all-users-group! (mt/db))
+          (mt/with-temporary-setting-values [slack-token nil]
+            (is (= nil
+                   (-> ((mt/user->client :rasta) :get 200 "pulse/form_input")
+                       (get-in [:channels :slack]))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/table_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/table_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0a8a2a062458fa268e1308f525ec118b71a1756b
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/table_test.clj
@@ -0,0 +1,37 @@
+(ns metabase-enterprise.sandbox.api.table-test
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase.test :as mt]))
+
+(def ^:private all-columns
+  #{"CATEGORY_ID" "ID" "LATITUDE" "LONGITUDE" "NAME" "PRICE"})
+
+(deftest query-metadata-test
+  (testing "GET /api/table/:id/query_metadata"
+    (letfn [(field-names [test-user]
+              (let [{:keys [fields], :as response} ((mt/user->client test-user) :get 200
+                                                    (format "table/%d/query_metadata" (mt/id :venues)))]
+                (if (seq fields)
+                  (set (map (comp str/upper-case :name) fields))
+                  response)))]
+      (mt.tu/with-segmented-test-setup mt.tu/restricted-column-query
+        (mt.tu/with-user-attributes :rasta {:cat 50}
+          (testing "Users with restricted access to the columns of a table should only see columns included in the GTAP question"
+            (is (= #{"CATEGORY_ID" "ID" "NAME"}
+                   (field-names :rasta))))
+
+          (testing "Users with full permissions should not be affected by this field filtering"
+            (is (= all-columns
+                   (field-names :crowberto))))))
+
+      (testing (str "If a GTAP has a question, but that question doesn't include a clause to restrict the columns that "
+                    "are returned, all fields should be returned")
+        (mt.tu/with-segmented-test-setup (fn [db-id]
+                                           {:database db-id
+                                            :type     :query
+                                            :query    {:source_table (mt/id :venues)}})
+          (mt.tu/with-user-attributes :rasta {:cat 50}
+            (is (= all-columns
+                   (field-names :rasta)))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/user_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/user_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..484f42582fc3d45929e30b44127e4cceb418f927
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/user_test.clj
@@ -0,0 +1,41 @@
+(ns metabase-enterprise.sandbox.api.user-test
+  "Tests that would logically be included in `metabase.api.user-test` but are separate as they are enterprise only."
+  (:require [clojure.test :refer :all]
+            [metabase
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase-enterprise.sandbox.test-util :as sandbox.tu]
+            [metabase.models
+             [card :refer [Card]]
+             [permissions-group :as perms-group :refer [PermissionsGroup]]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]]
+            [metabase.test
+             [fixtures :as fixtures]
+             [util :as tu]]))
+
+(use-fixtures :once (fixtures/initialize :test-users-personal-collections))
+
+;; Non-segmented users are allowed to ask for a list of all of the users in the Metabase instance. Pulse email lists
+;; are an example usage of this. Segmented users should not have that ability. Instead they should only see
+;; themselves. This test checks that GET /api/user for a segmented user only returns themselves
+(deftest segmented-user-list-test
+  (testing "GET /api/user"
+    (mt/with-temp-copy-of-db
+      (mt/with-temp* [Card [{card-id :id :as card} {:name          "magic"
+                                                    :dataset_query {:database (u/get-id (mt/db))
+                                                                    :type     :native
+                                                                    :native   {:query         "SELECT * FROM VENUES WHERE category_id = {{cat}}"
+                                                                               :template_tags {:cat {:name "cat" :display_name "cat" :type "number" :required true}}}}}]
+                      PermissionsGroup [{group-id :id} {:name "Restricted Venues"}]
+                      PermissionsGroupMembership [_ {:group_id group-id
+                                                     :user_id  (mt/user->id :rasta)}]
+                      GroupTableAccessPolicy [gtap {:group_id             group-id
+                                                    :table_id             (mt/id :venues)
+                                                    :card_id              card-id
+                                                    :attribute_remappings {:cat ["variable" ["template-tag" "cat"]]}}]]
+
+        (sandbox.tu/add-segmented-perms-for-venues-for-all-users-group! (mt/db))
+        ;; Now do the request
+        (is (= [{:common_name "Rasta Toucan", :last_name "Toucan", :first_name "Rasta", :email "rasta@metabase.com", :id true}]
+               (tu/boolean-ids-and-timestamps ((mt/user->client :rasta) :get 200 "user"))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/util_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/util_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..3c570277d5b259b1cfef3f8893cd6c37975eb694
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/util_test.clj
@@ -0,0 +1,21 @@
+(ns metabase-enterprise.sandbox.api.util-test
+  (:require [clojure.test :refer :all]
+            [metabase-enterprise.sandbox.api.util :as mt.api.u]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase.test :as mt]
+            [metabase.test.data.users :as test.users]))
+
+(defn- has-segmented-perms-when-segmented-db-exists? [user-kw]
+  (mt/with-temp-copy-of-db
+    (mt.tu/add-segmented-perms-for-venues-for-all-users-group! (mt/db))
+    (test.users/with-test-user user-kw
+      (mt.api.u/segmented-user?))))
+
+(deftest never-segment-admins-test
+  (testing "Admins should not be classified as segmented users -- enterprise #147"
+    (testing "Non-admin"
+      (is (= true
+             (has-segmented-perms-when-segmented-db-exists? :rasta))))
+    (testing "admin"
+      (is (=  false
+              (has-segmented-perms-when-segmented-db-exists? :crowberto))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/models/group_table_access_policy_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/models/group_table_access_policy_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..706f8588a833f248d7dab9d3532cb3a40548c1d6
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/models/group_table_access_policy_test.clj
@@ -0,0 +1,29 @@
+(ns metabase-enterprise.sandbox.models.group-table-access-policy-test
+  (:require [expectations :refer [expect]]
+            [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.models.permissions-group :as group]
+            [metabase.test.data :as data]
+            [metabase.util :as u]
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
+
+;; make sure attribute-remappings come back from the DB normalized the way we'd expect
+(expect
+  {"venue_id" {:type   :category
+               :target [:variable [:field-id (data/id :venues :id)]]
+               :value  5}}
+  (tt/with-temp GroupTableAccessPolicy [gtap {:table_id             (data/id :venues)
+                                              :group_id             (u/get-id (group/all-users))
+                                              :attribute_remappings {"venue_id" {:type   "category"
+                                                                                 :target ["variable" ["field-id" (data/id :venues :id)]]
+                                                                                 :value  5}}}]
+    (db/select-one-field :attribute_remappings GroupTableAccessPolicy :id (u/get-id gtap))))
+
+;; apparently sometimes they are saved with just the target, but not type or value? Make sure these get normalized
+;; correctly.
+(expect
+  {"user" [:variable [:field-id (data/id :venues :id)]]}
+  (tt/with-temp GroupTableAccessPolicy [gtap {:table_id             (data/id :venues)
+                                              :group_id             (u/get-id (group/all-users))
+                                              :attribute_remappings {"user" ["variable" ["field-id" (data/id :venues :id)]]}}]
+    (db/select-one-field :attribute_remappings GroupTableAccessPolicy :id (u/get-id gtap))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..a6abaaa69cf431f707edf162fe7b8438597404b2
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj
@@ -0,0 +1,157 @@
+(ns metabase-enterprise.sandbox.pulse-test
+  (:require [clojure.data.csv :as csv]
+            [clojure.java.io :as io]
+            [clojure.test :refer :all]
+            [medley.core :as m]
+            [metabase
+             [models :refer [Card Pulse PulseCard PulseChannel PulseChannelRecipient]]
+             [pulse :as pulse]
+             [query-processor :as qp]
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase.email.messages :as messages]
+            [metabase.models.pulse :as models.pulse]
+            [metabase.pulse.test-util :as pulse.tu]))
+
+(deftest sandboxed-pulse-test
+  (testing "Pulses should get sent with the row-level restrictions of the User that created them."
+    (letfn [(send-pulse-created-by-user! [user-kw]
+              (mt.tu/with-gtaps {:gtaps      {:venues {:query      (mt/mbql-query venues)
+                                                       :remappings {:cat ["variable" [:field-id (mt/id :venues :category_id)]]}}}
+                                 :attributes {"cat" 50}}
+                (mt/with-temp Card [card {:dataset_query (mt/mbql-query venues {:aggregation [[:count]]})}]
+                  ;; `with-gtaps` binds the current test user; we don't want that falsely affecting results
+                  (mt/with-test-user nil
+                    (pulse.tu/send-pulse-created-by-user! user-kw card)))))]
+      (is (= [[100]]
+             (send-pulse-created-by-user! :crowberto)))
+      (is (= [[10]]
+             (send-pulse-created-by-user! :rasta))))))
+
+(defn- pulse-results
+  "Results for creating and running a Pulse."
+  [query]
+  (mt/with-temp* [Card                  [pulse-card {:dataset_query query}]
+                  Pulse                 [pulse {:name "Test Pulse"}]
+                  PulseCard             [_ {:pulse_id (:id pulse), :card_id (:id pulse-card)}]
+                  PulseChannel          [pc {:channel_type :email
+                                             :pulse_id     (:id pulse)
+                                             :enabled      true}]
+                  PulseChannelRecipient [_ {:pulse_channel_id (:id pc)
+                                            :user_id          (mt/user->id :rasta)}]]
+    (mt/with-temporary-setting-values [email-from-address "metamailman@metabase.com"]
+      (mt/with-fake-inbox
+        (with-redefs [messages/render-pulse-email (fn [_ _ [{:keys [result]}]]
+                                                    [{:result result}])]
+          (mt/with-test-user nil
+            (pulse/send-pulse! pulse)))
+        (let [results @mt/inbox]
+          (is (= {"rasta@metabase.com" [{:from    "metamailman@metabase.com"
+                                         :to      ["rasta@metabase.com"]
+                                         :subject "Pulse: Test Pulse"}]}
+                 (m/dissoc-in results ["rasta@metabase.com" 0 :body])))
+          (get-in results ["rasta@metabase.com" 0 :body 0 :result]))))))
+
+(deftest e2e-sandboxed-pulse-test
+  (testing "Sending Pulses w/ sandboxing, end-to-end"
+    (mt.tu/with-gtaps {:gtaps {:venues {:query (mt/mbql-query venues
+                                                 {:filter [:= $price 3]})}}}
+      (let [query (mt/mbql-query venues
+                    {:aggregation [[:count]]
+                     :breakout    [$price]})]
+        (is (= [[3 13]]
+               (mt/formatted-rows [int int]
+                 (mt/with-test-user :rasta
+                   (qp/process-query query))))
+            "Basic sanity check: make sure the query is properly set up to apply GTAPs")
+        (testing "GTAPs should apply to Pulses — they should get the same results as if running that query normally"
+          (is (= [[3 13]]
+                 (mt/rows
+                   (pulse-results query)))))))))
+
+(defn- html->row-count [html]
+  (or (some->> html (re-find #"of <strong.+>(\d+)</strong> rows") second Integer/parseUnsignedInt)
+      html))
+
+(defn- csv->row-count [attachment-url]
+  (when attachment-url
+    (with-open [reader (io/reader attachment-url)]
+      (count (csv/read-csv reader)))))
+
+(deftest user-attributes-test
+  (testing "Pulses should be sandboxed correctly by User login_attributes"
+    (mt.tu/with-gtaps {:gtaps      {:venues {:remappings {:price [:dimension [:field-id (mt/id :venues :price)]]}}}
+                       :attributes {"price" "1"}}
+      (let [query (mt/mbql-query venues)]
+        (mt/with-test-user :rasta
+          (mt/with-temp Card [card {:dataset_query query}]
+            (testing "Sanity check: make sure user is seeing sandboxed results outside of Pulses"
+              (testing "ad-hoc query"
+                (is (= 22
+                       (count (mt/rows (qp/process-query query))))))
+
+              (testing "in a Saved Question"
+                (is (= 22
+                       (count (mt/rows ((mt/user->client :rasta) :post 202 (format "card/%d/query" (u/get-id card)))))))))
+
+            (testing "Pulse should be sandboxed"
+              (is (= 22
+                     (count (mt/rows (pulse-results query))))))))))))
+
+(deftest pulse-preview-test
+  (testing "Pulse preview endpoints should be sandboxed"
+    (mt.tu/with-gtaps {:gtaps      {:venues {:remappings {:price [:dimension [:field-id (mt/id :venues :price)]]}}}
+                       :attributes {"price" "1"}}
+      (let [query (mt/mbql-query venues)]
+        (mt/with-test-user :rasta
+          (mt/with-temp Card [card {:dataset_query query}]
+            (testing "GET /api/pulse/preview_card/:id"
+              (is (= 22
+                     (html->row-count ((mt/user->client :rasta) :get 200 (format "pulse/preview_card/%d" (u/get-id card)))))))
+
+            (testing "POST /api/pulse/test"
+              (mt/with-fake-inbox
+                ((mt/user->client :rasta) :post 200 "pulse/test" {:name     "venues"
+                                                                  :cards    [{:id          (u/get-id card)
+                                                                              :include_csv true
+                                                                              :include_xls false}]
+                                                                  :channels [{:channel_type :email
+                                                                              :enabled      :true
+                                                                              :recipients   [{:id    (mt/user->id :rasta)
+                                                                                              :email "rasta@metabase.com"}]}]})
+                (let [[{html :content} {attachment :content}] (get-in @mt/inbox ["rasta@metabase.com" 0 :body])]
+                  (testing "email"
+                    (is (= 22
+                           (html->row-count html))))
+                  (testing "CSV attachment"
+                    ;; one extra row because first row is column names
+                    (is (= 23
+                           (csv->row-count attachment)))))))))))))
+
+(deftest csv-downloads-test
+  (testing "CSV/XLSX downloads should be sandboxed"
+    (mt.tu/with-gtaps {:gtaps      {:venues {:remappings {:price [:dimension [:field-id (mt/id :venues :price)]]}}}
+                       :attributes {"price" "1"}}
+      (let [query (mt/mbql-query venues)]
+        (mt/with-test-user :rasta
+          (mt/with-temp* [Card                 [{card-id :id}  {:dataset_query query}]
+                          Pulse                [{pulse-id :id} {:name          "Pulse Name"
+                                                                :skip_if_empty false}]
+                          PulseCard             [_             {:pulse_id pulse-id
+                                                                :card_id  card-id
+                                                                :position 0}]
+                          PulseChannel          [{pc-id :id}   {:pulse_id pulse-id}]
+                          PulseChannelRecipient [_             {:user_id          (mt/user->id :rasta)
+                                                                :pulse_channel_id pc-id}]]
+            (mt/with-fake-inbox
+             (mt/with-test-user nil
+               (pulse/send-pulse! (models.pulse/retrieve-pulse pulse-id)))
+             (let [email-results                           @mt/inbox
+                   [{html :content} {attachment :content}] (get-in email-results ["rasta@metabase.com" 0 :body])]
+               (testing "email"
+                 (is (= 22
+                        (html->row-count html))))
+               (testing "CSV attachment"
+                 (is (= 23
+                        (csv->row-count attachment))))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..457b3477ec7b7b1c47b07435e6eb5219f57d551a
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj
@@ -0,0 +1,553 @@
+(ns metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions-test
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
+            [honeysql.core :as hsql]
+            [metabase
+             [driver :as driver]
+             [models :refer [Card Collection Field Table]]
+             [query-processor :as qp]
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions :as row-level-restrictions]
+            [metabase-enterprise.sandbox.test-util :as mt.tu]
+            [metabase.driver.sql.query-processor :as sql.qp]
+            [metabase.mbql
+             [normalize :as normalize]
+             [util :as mbql.u]]
+            [metabase.models
+             [permissions :as perms]
+             [permissions-group :as perms-group]]
+            [metabase.query-processor.util :as qputil]
+            [metabase.test.data.env :as tx.env]
+            [metabase.test.util :as tu]
+            [metabase.util.honeysql-extensions :as hx]))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                      SHARED GTAP DEFINITIONS & HELPER FNS                                      |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- identifier
+  ([table-key]
+   (mt/with-everything-store
+     (sql.qp/->honeysql (or driver/*driver* :h2) (Table (mt/id table-key)))))
+
+  ([table-key field-key]
+   (mt/with-everything-store
+     (sql.qp/->honeysql (or driver/*driver* :h2) (Field (mt/id table-key field-key))))))
+
+(defn- venues-category-mbql-gtap-def []
+  {:query      (mt/mbql-query venues)
+   :remappings {:cat ["variable" [:field-id (mt/id :venues :category_id)]]}})
+
+(defn- venues-price-mbql-gtap-def []
+  {:query      (mt/mbql-query venues)
+   :remappings {:price ["variable" [:field-id (mt/id :venues :price)]]}})
+
+(defn- checkins-user-mbql-gtap-def []
+  {:query      (mt/mbql-query checkins {:filter [:> $date "2014-01-01"]})
+   :remappings {:user ["variable" [:field-id (mt/id :checkins :user_id)]]}})
+
+(defn- format-honeysql [honeysql]
+  (let [honeysql (cond-> honeysql
+                   (= driver/*driver* :sqlserver)
+                   (assoc :modifiers ["TOP 1000"])
+
+                   ;; SparkSQL has to have an alias source table (or at least our driver is written as if it has to
+                   ;; have one.) HACK
+                   (= driver/*driver* :sparksql)
+                   (update :from (fn [[table]]
+                                   [[table (sql.qp/->honeysql :sparksql
+                                             (hx/identifier :table-alias @(resolve 'metabase.driver.sparksql/source-table-alias)))]])))]
+    (first (hsql/format honeysql, :quoting (sql.qp/quote-style driver/*driver*), :allow-dashed-names? true))))
+
+(defn- venues-category-native-gtap-def []
+  (driver/with-driver (or driver/*driver* :h2)
+    (assert (driver/supports? driver/*driver* :native-parameters))
+    {:query (mt/native-query
+              {:query
+               (format-honeysql
+                {:select   [:*]
+                 :from     [(identifier :venues)]
+                 :where    [:= (identifier :venues :category_id) (hsql/raw "{{cat}}")]
+                 :order-by [(identifier :venues :id)]})
+
+               :template_tags
+               {:cat {:name "cat" :display_name "cat" :type "number" :required true}}})
+     :remappings {:cat ["variable" ["template-tag" "cat"]]}}))
+
+(defn- parameterized-sql-with-join-gtap-def []
+  (driver/with-driver (or driver/*driver* :h2)
+    (assert (driver/supports? driver/*driver* :native-parameters))
+    {:query (mt/native-query
+              {:query
+               (format-honeysql
+                {:select    [(identifier :checkins :id)
+                             (identifier :checkins :user_id)
+                             (identifier :venues :name)
+                             (identifier :venues :category_id)]
+                 :from      [(identifier :checkins)]
+                 :left-join [(identifier :venues)
+                             [:= (identifier :checkins :venue_id) (identifier :venues :id)]]
+                 :where     [:= (identifier :checkins :user_id) (hsql/raw "{{user}}")]
+                 :order-by  [[(identifier :checkins :id) :asc]]})
+
+               :template_tags
+               {"user" {:name         "user"
+                        :display-name "User ID"
+                        :type         :number
+                        :required     true}}})
+     :remappings {:user ["variable" ["template-tag" "user"]]}}))
+
+(defn- venue-names-native-gtap-def []
+  {:query (mt/native-query
+            {:query
+             (format-honeysql
+              {:select   [(identifier :venues :name)]
+               :from     [(identifier :venues)]
+               :order-by [(identifier :venues :id)]})})})
+
+(defn- run-venues-count-query []
+  (mt/format-rows-by [int]
+    (mt/rows
+      (mt/run-mbql-query venues {:aggregation [[:count]]}))))
+
+(defn- run-checkins-count-broken-out-by-price-query []
+  (mt/format-rows-by [#(some-> % int) int]
+    (mt/rows
+      (mt/run-mbql-query checkins
+        {:aggregation [[:count]]
+         :order-by    [[:asc $venue_id->venues.price]]
+         :breakout    [$venue_id->venues.price]}))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                MIDDLEWARE TESTS                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(deftest all-table-ids-test
+  (testing (str "make sure that `all-table-ids` can properly find all Tables in the query, even in cases where a map "
+                "has a `:source-table` and some of its children also have a `:source-table`"))
+  (is (= (mt/$ids nil
+           #{$$checkins $$venues $$users $$categories})
+         (#'row-level-restrictions/all-table-ids
+          (mt/mbql-query nil
+            {:source-table $$checkins
+             :joins        [{:source-table $$venues}
+                            {:source-query {:source-table $$users
+                                            :joins        [{:source-table $$categories}]}}]})))))
+
+(defn- remove-metadata [m]
+  (mbql.u/replace m
+    (_ :guard (every-pred map? :source-metadata))
+    (remove-metadata (dissoc &match :source-metadata))))
+
+(defn- apply-row-level-permissions [query]
+  (-> (mt/with-everything-store
+        (mt/test-qp-middleware row-level-restrictions/apply-row-level-permissions (normalize/normalize query)))
+      :pre
+      remove-metadata))
+
+(deftest middleware-test
+  (testing "Make sure the middleware does the correct transformation given the GTAPs we have"
+    (mt/with-gtaps {:gtaps      {:checkins (checkins-user-mbql-gtap-def)
+                                 :venues   (dissoc (venues-price-mbql-gtap-def) :query)}
+                    :attributes {"user" 5, "price" 1}}
+      (testing "Should add a filter for attributes-only GTAP"
+        (is (= (mt/query checkins
+                 {:type       :query
+                  :query      {:source-query {:source-table $$checkins
+                                              :fields       [$id !default.$date $user_id $venue_id]
+                                              :filter       [:and
+                                                             [:> $date [:absolute-datetime #t "2014-01-01T00:00Z[UTC]" :default]]
+                                                             [:=
+                                                              $user_id
+                                                              [:value 5 {:base_type     :type/Integer
+                                                                         :special_type  :type/FK
+                                                                         :database_type "INTEGER"
+                                                                         :name          "USER_ID"}]]]
+                                              :gtap?        true}
+                               :joins        [{:source-query
+                                               {:source-table $$venues
+                                                :fields       [$venues.id $venues.name $venues.category_id
+                                                               $venues.latitude $venues.longitude $venues.price]
+                                                :filter       [:=
+                                                               $venues.price
+                                                               [:value 1 {:base_type     :type/Integer
+                                                                          :special_type  :type/Category
+                                                                          :database_type "INTEGER"
+                                                                          :name          "PRICE"}]]
+                                                :gtap?        true}
+                                               :alias     "v"
+                                               :strategy  :left-join
+                                               :condition [:= $venue_id &v.venues.id]}]
+                               :aggregation  [[:count]]}
+                  :gtap-perms #{(perms/table-query-path (Table (mt/id :venues)))
+                                (perms/table-query-path (Table (mt/id :checkins)))}})
+               (apply-row-level-permissions
+                (mt/mbql-query checkins
+                  {:aggregation [[:count]]
+                   :joins       [{:source-table $$venues
+                                  :alias        "v"
+                                  :strategy     :left-join
+                                  :condition    [:= $venue_id &v.venues.id]}]}))))))
+
+    (testing "Should substitute appropriate value in native query"
+      (mt.tu/with-gtaps {:gtaps      {:venues (venues-category-native-gtap-def)}
+                         :attributes {"cat" 50}}
+        (is (= (mt/query nil
+                 {:database   (mt/id)
+                  :type       :query
+                  :query      {:aggregation  [[:count]]
+                               :source-query {:native (str "SELECT * FROM \"PUBLIC\".\"VENUES\" "
+                                                           "WHERE \"PUBLIC\".\"VENUES\".\"CATEGORY_ID\" = 50 "
+                                                           "ORDER BY \"PUBLIC\".\"VENUES\".\"ID\"")
+                                              :params []}}
+                  :gtap-perms #{(perms/adhoc-native-query-path (mt/id))}})
+               (apply-row-level-permissions
+                (mt/mbql-query venues
+                  {:aggregation [[:count]]}))))))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                END-TO-END TESTS                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(deftest e2e-test
+  (mt/test-drivers (mt/normal-drivers-with-feature :nested-queries)
+    (testing "When querying with full permissions, no changes should be made"
+      (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                      :attributes {"cat" 50}}
+        (perms/grant-permissions! &group (perms/table-query-path (Table (mt/id :venues))))
+        (is (= [[100]]
+               (run-venues-count-query)))))
+
+    (testing (str "Basic test around querying a table by a user with segmented only permissions and a GTAP question that "
+                  "is a native query")
+      (mt/with-gtaps {:gtaps      {:venues (venues-category-native-gtap-def)}
+                      :attributes {"cat" 50}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing (str "Basic test around querying a table by a user with segmented only permissions and a GTAP question that "
+                  "is MBQL")
+      (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                      :attributes {"cat" 50}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing (str "When processing a query that requires a user attribute and that user attribute isn't there, throw an "
+                  "exception letting the user know it's missing")
+      (is (thrown-with-msg?
+           clojure.lang.ExceptionInfo
+           #"Query requires user attribute `cat`"
+           (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                           :attributes {"something_random" 50}}
+             (mt/run-mbql-query venues {:aggregation [[:count]]})))))
+
+    (testing "Another basic test, same as above, but with a numeric string that needs to be coerced"
+      (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                      :attributes {"cat" "50"}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing "Another basic test, this one uses a stringified float for the login attribute"
+      (mt/with-gtaps {:gtaps      {:venues {:query      (mt/mbql-query venues)
+                                            :remappings {:cat ["variable" [:field-id (mt/id :venues :latitude)]]}}}
+                      :attributes {"cat" "34.1018"}}
+        (is (= [[3]]
+               (run-venues-count-query)))))
+
+    (testing "Tests that users can have a different parameter name in their query than they have in their user attributes"
+      (mt/with-gtaps {:gtaps      {:venues {:query      (:query (venues-category-native-gtap-def))
+                                            :remappings {:something.different ["variable" ["template-tag" "cat"]]}}}
+                      :attributes {"something.different" 50}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing "Make sure that you can still use a SQL-based GTAP without needing to have SQL read perms for the Database"
+      (is (= [["Red Medicine"] ["Stout Burgers & Beers"]]
+             (mt/rows
+               (mt/with-gtaps {:gtaps {:venues (venue-names-native-gtap-def)}}
+                 (mt/run-mbql-query venues {:limit 2}))))))
+
+    (testing (str "When no card_id is included in the GTAP, should default to a query against the table, with the GTAP "
+                  "criteria applied")
+      (mt/with-gtaps {:gtaps      {:venues (dissoc (venues-category-mbql-gtap-def) :query)}
+                      :attributes {"cat" 50}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing "Same test as above but make sure we coerce a numeric string correctly"
+      (mt/with-gtaps {:gtaps      {:venues (dissoc (venues-category-mbql-gtap-def) :query)}
+                      :attributes {"cat" "50"}}
+        (is (= [[10]]
+               (run-venues-count-query)))))
+
+    (testing "Users with view access to the related collection should bypass segmented permissions"
+      (mt/with-temp-copy-of-db
+        (mt/with-temp* [Collection [collection]
+                        Card       [card        {:collection_id (u/get-id collection)}]]
+          (mt.tu/with-group [group]
+            (perms/revoke-permissions! (perms-group/all-users) (mt/id))
+            (perms/grant-collection-read-permissions! group collection)
+            (mt/with-test-user :rasta
+              (is (= 1
+                     (count
+                      (mt/rows
+                        (qp/process-query
+                         {:database (mt/id)
+                          :type     :query
+                          :query    {:source-table (mt/id :venues)
+                                     :limit        1}
+                          :info     {:card-id    (u/get-id card)
+                                     :query-hash (byte-array 0)}}))))))))))
+
+    (testing (str "This test isn't covering a row level restrictions feature, but rather checking it it doesn't break "
+                  "querying of a card as a nested query. Part of the row level perms check is looking at the table (or "
+                  "card) to see if row level permissions apply. This was broken when it wasn't expecting a card and "
+                  "only expecting resolved source-tables")
+      (mt/with-temp Card [card {:dataset_query (mt/mbql-query venues)}]
+        (mt/with-test-user :rasta
+          (is (= [[100]]
+                 (mt/format-rows-by [int]
+                   (mt/rows
+                     (qp/process-query
+                      {:database (mt/id)
+                       :type     :query
+                       :query    {:source-table (format "card__%s" (u/get-id card))
+                                  :aggregation  [["count"]]}}))))))))))
+
+;; Test that we can follow FKs to related tables and breakout by columns on those related tables. This test has
+;; several things wrapped up which are detailed below
+
+(defn- row-level-restrictions-fk-drivers
+  "Drivers to test row-level restrictions against foreign keys with. Includes BigQuery, which for whatever reason does
+  not normally have FK tests ran for it."
+  []
+  (cond-> (mt/normal-drivers-with-feature :nested-queries :foreign-keys)
+    (@tx.env/test-drivers :bigquery) (conj :bigquery)))
+
+;; HACK - Since BigQuery doesn't formally support foreign keys (meaning we can't sync them automatically), FK tests
+;; are disabled by default for BigQuery. We really want to test them here! The macros below let us "fake" FK support
+;; for BigQuery.
+(defn- do-enable-bigquery-fks [f]
+  (let [supports? driver/supports?]
+    (with-redefs [driver/supports? (fn [driver feature]
+                                     (if (= [driver feature] [:bigquery :foreign-keys])
+                                       true
+                                       (supports? driver feature)))]
+      (f))))
+
+(defmacro ^:private enable-bigquery-fks [& body]
+  `(do-enable-bigquery-fks (fn [] ~@body)))
+
+(defn- do-with-bigquery-fks [f]
+  (if-not (= driver/*driver* :bigquery)
+    (f)
+    (tu/with-temp-vals-in-db Field (mt/id :checkins :user_id) {:fk_target_field_id (mt/id :users :id)
+                                                                 :special_type       "type/FK"}
+      (tu/with-temp-vals-in-db Field (mt/id :checkins :venue_id) {:fk_target_field_id (mt/id :venues :id)
+                                                                    :special_type       "type/FK"}
+        (f)))))
+
+(defmacro ^:private with-bigquery-fks [& body]
+  `(do-with-bigquery-fks (fn [] ~@body)))
+
+(deftest e2e-fks-test
+  (mt/test-drivers (row-level-restrictions-fk-drivers)
+    (enable-bigquery-fks
+     (testing (str "1 - Creates a GTAP filtering question, looking for any checkins happening on or after 2014\n"
+                   "2 - Apply the `user` attribute, looking for only our user (i.e. `user_id` =  5)\n"
+                   "3 - Checkins are related to Venues, query for checkins, grouping by the Venue's price\n"
+                   "4 - Order by the Venue's price to ensure a predictably ordered response")
+       (mt/with-gtaps {:gtaps      {:checkins (checkins-user-mbql-gtap-def)
+                                    :venues   nil}
+                       :attributes {"user" 5}}
+         (with-bigquery-fks
+           (is (= [[1 10] [2 36] [3 4] [4 5]]
+                  (run-checkins-count-broken-out-by-price-query))))))
+
+     (testing (str "Test that we're able to use a GTAP for an FK related table. For this test, the user has segmented "
+                   "permissions on checkins and venues, so we need to apply a GTAP to the original table (checkins) in "
+                   "addition to the related table (venues). This test uses a GTAP question for both tables")
+       (mt/with-gtaps {:gtaps      {:checkins (checkins-user-mbql-gtap-def)
+                                    :venues   (venues-price-mbql-gtap-def)}
+                       :attributes {"user" 5, "price" 1}}
+         (with-bigquery-fks
+           (is (= #{[nil 45] [1 10]}
+                  (set (run-checkins-count-broken-out-by-price-query)))))))
+
+     (testing "Test that the FK related table can be a \"default\" GTAP, i.e. a GTAP where the `card_id` is nil"
+       (mt/with-gtaps {:gtaps      {:checkins (checkins-user-mbql-gtap-def)
+                                    :venues   (dissoc (venues-price-mbql-gtap-def) :query)}
+                       :attributes {"user" 5, "price" 1}}
+         (with-bigquery-fks
+           (is (= #{[nil 45] [1 10]}
+                  (set (run-checkins-count-broken-out-by-price-query)))))))
+
+     (testing (str "Test that we have multiple FK related, segmented tables. This test has checkins with a GTAP "
+                   "question with venues and users having the default GTAP and segmented permissions")
+       (mt/with-gtaps {:gtaps      {:checkins (checkins-user-mbql-gtap-def)
+                                    :venues   (dissoc (venues-price-mbql-gtap-def) :query)
+                                    :users    {:remappings {:user ["variable" [:field-id (mt/id :users :id)]]}}}
+                       :attributes {"user" 5, "price" 1}}
+         (with-bigquery-fks
+           (is (= #{[nil "Quentin Sören" 45] [1 "Quentin Sören" 10]}
+                  (set
+                   (mt/format-rows-by [#(when % (int %)) str int]
+                     (mt/rows
+                       (mt/run-mbql-query checkins
+                         {:aggregation [[:count]]
+                          :order-by    [[:asc $venue_id->venues.price]]
+                          :breakout    [$venue_id->venues.price $user_id->users.name]}))))))))))))
+
+(defn- run-query-returning-remark [run-query-fn]
+  (let [remark (atom nil)
+        orig   qputil/query->remark]
+    (with-redefs [qputil/query->remark (fn [driver outer-query]
+                                         (u/prog1 (orig driver outer-query)
+                                           (reset! remark <>)))]
+      (let [results (run-query-fn)]
+        (or (some-> @remark (str/replace #"queryHash: \w+" "queryHash: <hash>"))
+            (println "NO REMARK FOUND:\n" (u/pprint-to-str 'red results))
+            (throw (ex-info "No remark found!" {:results results})))))))
+
+(deftest remark-test
+  (testing "make sure GTAP queries still include ID of user who ran them in the remark"
+    (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                    :attributes {"cat" 50}}
+      (is (= (format "Metabase:: userID: %d queryType: MBQL queryHash: <hash>" (mt/user->id :rasta))
+             (run-query-returning-remark
+              (fn []
+                (mt/user-http-request :rasta :post "dataset" (mt/mbql-query venues {:aggregation [[:count]]})))))))))
+
+(deftest breakouts-test
+  (mt/test-drivers (row-level-restrictions-fk-drivers)
+    (testing "Make sure that if a GTAP is in effect we can still do stuff like breakouts (#229)"
+      (mt/with-gtaps {:gtaps      {:venues (venues-category-native-gtap-def)}
+                      :attributes {"cat" 50}}
+        (is (= [[1 6] [2 4]]
+               (mt/format-rows-by [int int]
+                 (mt/rows
+                   (mt/run-mbql-query venues
+                     {:aggregation [[:count]]
+                      :breakout    [$price]})))))))))
+
+(deftest sql-with-join-test
+  (mt/test-drivers (row-level-restrictions-fk-drivers)
+    (testing (str "If we use a parameterized SQL GTAP that joins a Table the user doesn't have access to, does it "
+                  "still work? (EE #230) If we pass the query in directly without anything that would require nesting "
+                  "it, it should work")
+      (is (= [[2  1 "Bludso's BBQ" 5]
+              [72 1 "Red Medicine" 4]]
+             (mt/format-rows-by [int int identity int]
+               (mt/rows
+                 (mt/with-gtaps {:gtaps      {:checkins (parameterized-sql-with-join-gtap-def)}
+                                 :attributes {"user" 1}}
+                   (mt/run-mbql-query checkins
+                     {:limit 2})))))))
+
+    (testing (str "#230: If we modify the query in a way that would cause the original to get nested as a source query, "
+                  "do things work?")
+      (is (= [[5 69]]
+             (mt/format-rows-by [int int]
+               (mt/rows
+                 (mt/with-gtaps {:gtaps      {:checkins (parameterized-sql-with-join-gtap-def)}
+                                 :attributes {"user" 5}}
+                   (mt/run-mbql-query checkins
+                     {:aggregation [[:count]]
+                      :breakout    [$user_id]})))))))))
+
+(deftest correct-metadata-test
+  (testing (str "We should return the same metadata as the original Table when running a query against a sandboxed "
+                "Table (#390)\n")
+    (let [cols          (fn []
+                          (mt/cols
+                            (mt/run-mbql-query venues
+                              {:order-by [[:asc $id]]
+                               :limit    2})))
+          original-cols (cols)
+          ;; `with-gtaps` copies the test DB so this function will update the IDs in `original-cols` so they'll match
+          ;; up with the current copy
+          expected-cols (fn []
+                          (for [col  original-cols
+                                :let [id (mt/id :venues (keyword (str/lower-case (:name col))))]]
+                            (assoc col
+                                   :id id
+                                   :table_id (mt/id :venues)
+                                   :field_ref [:field-id id])))]
+      (testing "A query with a simple attributes-based sandbox should have the same metadata"
+        (mt/with-gtaps {:gtaps      {:venues (dissoc (venues-category-mbql-gtap-def) :query)}
+                        :attributes {"cat" 50}}
+            (is (= (expected-cols)
+                   (cols)))))
+
+      (testing "A query with an equivalent MBQL query sandbox should have the same metadata"
+        (mt/with-gtaps {:gtaps      {:venues (venues-category-mbql-gtap-def)}
+                        :attributes {"cat" 50}}
+            (is (= (expected-cols)
+                   (cols)))))
+
+      (testing "A query with an equivalent native query sandbox should have the same metadata"
+        (mt/with-gtaps {:gtaps {:venues {:query (mt/native-query
+                                                  {:query
+                                                   (str "SELECT ID, NAME, CATEGORY_ID, LATITUDE, LONGITUDE, PRICE "
+                                                        "FROM VENUES "
+                                                        "WHERE CATEGORY_ID = {{cat}}")
+
+                                                   :template_tags
+                                                   {:cat {:name "cat" :display_name "cat" :type "number" :required true}}})
+                                         :remappings {:cat ["variable" ["template-tag" "cat"]]}}}
+                        :attributes {"cat" 50}}
+          (is (= (expected-cols)
+                 (cols)))))
+
+      (testing (str "If columns are added/removed/reordered we should still merge in metadata for the columns we're "
+                      "able to match from the original Table")
+        (mt/with-gtaps {:gtaps {:venues {:query (mt/native-query
+                                                  {:query
+                                                   (str "SELECT NAME, ID, LONGITUDE, PRICE, 1 AS ONE "
+                                                        "FROM VENUES "
+                                                        "WHERE CATEGORY_ID = {{cat}}")
+
+                                                   :template_tags
+                                                   {:cat {:name "cat" :display_name "cat" :type "number" :required true}}})
+                                         :remappings {:cat ["variable" ["template-tag" "cat"]]}}}
+                        :attributes {"cat" 50}}
+          (let [[id-col name-col _ _ longitude-col price-col] (expected-cols)
+                one-col                                       {:name         "ONE"
+                                                               :display_name "ONE"
+                                                               :base_type    :type/Integer
+                                                               :source       :fields
+                                                               :field_ref    [:field-literal "ONE" :type/Integer]}]
+              (is (= [name-col id-col longitude-col price-col one-col]
+                     (cols)))))))))
+
+(deftest sandboxing-sql-with-joins-test
+  (testing "Should be able to use a Saved Question with no source Metadata as a GTAP (#525)"
+    (mt/with-gtaps (mt/$ids
+                     {:gtaps      {:venues   {:query      (mt/native-query
+                                                            {:query         (str "SELECT DISTINCT VENUES.* "
+                                                                                 "FROM VENUES "
+                                                                                 "LEFT JOIN CHECKINS"
+                                                                                 "       ON CHECKINS.VENUE_ID = VENUES.ID "
+                                                                                 "WHERE CHECKINS.USER_ID IN ({{sandbox}})")
+                                                             :template-tags {"sandbox"
+                                                                             {:name         "sandbox"
+                                                                              :display-name "Sandbox"
+                                                                              :type         :text}}})
+                                              :remappings {"user_id" [:variable [:template-tag "sandbox"]]}}
+                                   :checkins {:remappings {"user_id" [:dimension $checkins.user_id]}}}
+                      :attributes {"user_id" 1}})
+      (is (= [[2 "2014-09-18T00:00:00Z"  1 31 31 "Bludso's BBQ"         5 33.8894 -118.207 2]
+              [72 "2015-04-18T00:00:00Z" 1  1  1 "Red Medicine"         4 10.0646 -165.374 3]
+              [80 "2013-12-27T00:00:00Z" 1 99 99 "Golden Road Brewing" 10 34.1505 -118.274 2]]
+             (mt/rows
+               (mt/run-mbql-query checkins
+                 {:joins    [{:fields       :all
+                              :source-table $$venues
+                              :condition    [:= $venue_id [:joined-field "Venue" $venues.id]]
+                              :alias        "Venue"}]
+                  :order-by [[:asc $id]]
+                  :limit    3})))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj
new file mode 100644
index 0000000000000000000000000000000000000000..04e55ade4e7977da9397be234b1276b945db9016
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj
@@ -0,0 +1,154 @@
+(ns metabase-enterprise.sandbox.test-util
+  "Shared test utilities for multi-tenant tests."
+  (:require [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
+            [metabase.models
+             [card :refer [Card]]
+             [permissions :as perms]
+             [permissions-group :as perms-group :refer [PermissionsGroup]]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]
+             [table :refer [Table]]
+             [user :refer [User]]]
+            [metabase.test
+             [data :as data]
+             [util :as tu]]
+            [metabase.test.data.users :as users]
+            [metabase.util :as u]
+            [schema.core :as s]
+            [toucan.util.test :as tt]))
+
+(defmacro with-user-attributes
+  "Execute `body` with the attributes for a user temporarily set to `attributes-map`.
+
+    (with-user-attributes :rasta {\"cans\" 2}
+      ...)"
+  {:style/indent 2}
+  [user-kwd attributes-map & body]
+  `(tu/with-temp-vals-in-db User (users/user->id ~user-kwd) {:login_attributes ~attributes-map}
+     ~@body))
+
+(defn do-with-group [group f]
+  (tt/with-temp* [PermissionsGroup           [group group]
+                  PermissionsGroupMembership [_     {:group_id (u/get-id group)
+                                                     :user_id  (users/user->id :rasta)}]]
+    (f group)))
+
+(defmacro with-group
+  "Create a new PermissionsGroup, bound to `group-binding`; grant test user Rasta Toucan [RIP] permissions for the
+  group, then execute `body`."
+  [[group-binding group] & body]
+  `(do-with-group ~group (fn [~group-binding] ~@body)))
+
+
+(defn- do-with-gtap-defs
+  {:style/indent 2}
+  [group, [[table-kw {:keys [query remappings]} :as gtap-def] & more], f]
+  (if-not gtap-def
+    (f)
+    (let [do-with-card (fn [f]
+                         (if query
+                           (tt/with-temp Card [{card-id :id} {:dataset_query query}]
+                             (f card-id))
+                           (f nil)))]
+      (do-with-card
+       (fn [card-id]
+         (tt/with-temp GroupTableAccessPolicy [gtap {:group_id             (u/get-id group)
+                                                     :table_id             (data/id table-kw)
+                                                     :card_id              card-id
+                                                     :attribute_remappings remappings}]
+           (perms/grant-permissions! group (perms/table-segmented-query-path (Table (data/id table-kw))))
+           (do-with-gtap-defs group more f)))))))
+
+(def ^:private WithGTAPsArgs
+  "Schema for valid arguments to `with-gtaps`."
+  {:gtaps
+   {(s/named s/Keyword "Table") (s/maybe
+                                 {(s/optional-key :query)      (s/pred map?)
+                                  (s/optional-key :remappings) (s/pred map?)})}
+
+   (s/optional-key :attributes)
+   (s/pred map?)})
+
+(defn do-with-gtaps [args-fn f]
+  (data/with-temp-copy-of-db
+    (perms/revoke-permissions! (perms-group/all-users) (data/db))            ; remove perms for All Users group
+    (with-group [group]                                                      ; create new perms group
+      (let [{:keys [gtaps attributes]} (s/validate WithGTAPsArgs (args-fn))]
+        (with-user-attributes :rasta attributes                              ; set Rasta login_attributes
+          (do-with-gtap-defs group gtaps                                     ; create Cards/GTAPs from defs
+            (fn []
+              (users/with-test-user :rasta                                   ; bind Rasta as current user
+                (f group)))))))))                                            ; run (f)
+
+(defmacro with-gtaps
+  "Execute `body` with `gtaps` and optionally user `attributes` in effect. All underlying objects and permissions are
+  created automatically.
+
+  `gtaps-and-attributes-map` is a map containing `:gtaps` and optionally `:attributes`; see the `WithGTAPsArgs` schema
+  in this namespace.
+
+  *  `:gtaps` is a map of test ID table name -> gtap def. Both `:query` and `:remappings` are optional.
+
+  *  If `:query` is specified, a corresponding Card is created, and the GTAP is saved with that `:card_id`.
+     Otherwise Card ID is nil and the GTAP uses the source table directly.
+
+  *  `:remappings`, if specified, is saved as the `:attribute_remappings` property of the GTAP.
+
+    (mt.tu/with-gtaps {:gtaps      {:checkins {:query      {:database (data/id), ...}
+                                               :remappings {:user_category [\"variable\" ...]}}}
+                       :attributes {\"user_category\" 1}}
+      (data/run-mbql-query checkins {:limit 2}))"
+  {:style/indent 1}
+  [gtaps-and-attributes-map & body]
+  `(do-with-gtaps (fn [] ~gtaps-and-attributes-map) (fn [~'&group] ~@body)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            DEPRECATED HELPER MACROS                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn ^:deprecated add-segmented-perms-for-venues-for-all-users-group!
+  "Removes the default full permissions for all users and adds segmented and read permissions
+
+  DEPRECATED: Use `with-gtaps` macro instead, and you won't need to do this yourself."
+  [database-or-id]
+  (perms/revoke-permissions! (perms-group/all-users) database-or-id)
+  (perms/grant-permissions! (perms-group/all-users) (perms/table-read-path (Table (data/id :venues))))
+  (perms/grant-permissions! (perms-group/all-users) (perms/table-segmented-query-path (Table (data/id :venues)))))
+
+(defn ^:deprecated restricted-column-query [db-id]
+  {:database db-id
+   :type     :query
+   :query    (data/$ids venues
+               {:source_table $$venues
+                :fields       [[:field-id $id]
+                               [:field-id $name]
+                               [:field-id $category_id]]})})
+
+(defn ^:deprecated call-with-segmented-test-setup [make-query-fn f]
+  (data/with-temp-copy-of-db
+    (let [attr-remappings {:cat ["variable" [:field-id (data/id :venues :category_id)]]}]
+      (tt/with-temp* [Card                       [card  {:name          "magic"
+                                                         :dataset_query (make-query-fn (data/id))}]
+                      PermissionsGroup           [group {:name "Restricted Venues"}]
+                      PermissionsGroupMembership [_     {:group_id (u/get-id group)
+                                                         :user_id  (users/user->id :rasta)}]
+                      GroupTableAccessPolicy     [gtap  {:group_id             (u/get-id group)
+                                                         :table_id             (data/id :venues)
+                                                         :card_id              (u/get-id card)
+                                                         :attribute_remappings attr-remappings}]]
+        (add-segmented-perms-for-venues-for-all-users-group! (data/db))
+        (f)))))
+
+(defmacro ^:deprecated with-segmented-test-setup
+  "Helper for writing segmented permissions tests. Does the following:
+
+  1.  Creates copy of test data DB, binds it for use by `data/db` and `data/id`
+  2.  Creates a Card to serve as the GTAP for the `venues` Table. Card uses query created by calling `(make-query-fn db)`
+  3.  Creates a new Perms Group, and adds Rasta Toucan [RIP] to it
+  4.  Assigns GTAP to new perms group & `venues` Table
+  5.  Removes default full permissions for the DB for the 'All Users' Group, so GTAPs are used instead
+  6.  Runs `body`
+
+  DEPRECATED: Prefer `with-gtaps` instead, which is clearer and more flexible."
+  [make-query-fn & body]
+  `(call-with-segmented-test-setup ~make-query-fn (fn [] ~@body)))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..5c8aa2e6cb68214958af798d75413e3fb29ef1c9
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/serialization/load_test.clj
@@ -0,0 +1,68 @@
+(ns metabase-enterprise.serialization.load-test
+  (:refer-clojure :exclude [load])
+  (:require [clojure.data :as diff]
+            [clojure.java.io :as io]
+            [expectations :refer [expect]]
+            [metabase
+             [models :refer [Card Collection Dashboard DashboardCard DashboardCardSeries Database Dependency Dimension
+                             Field FieldValues Metric Pulse PulseCard PulseChannel Segment Table User]]
+             [util :as u]]
+            [metabase-enterprise.serialization
+             [cmd :refer [dump load]]
+             [test-util :as ts]]
+            [metabase.test.data.users :as test-users]
+            [toucan.db :as db])
+  (:import org.apache.commons.io.FileUtils))
+
+(defn- delete-directory!
+  [file-or-filename]
+  (FileUtils/deleteDirectory (io/file file-or-filename)))
+
+(def ^:private dump-dir "test-dump")
+
+(defn- world-snapshot
+  []
+  (into {} (for [model [Database Table Field Metric Segment Collection Dashboard DashboardCard Pulse
+                        Card DashboardCardSeries FieldValues Dimension Dependency PulseCard PulseChannel User]]
+             [model (db/select-field :id model)])))
+
+(defmacro with-world-cleanup
+  [& body]
+  `(let [snapshot# (world-snapshot)]
+     (try
+       ~@body
+       (finally
+         (doseq [[model# ids#] (second (diff/diff snapshot# (world-snapshot)))]
+           (some->> ids#
+                    not-empty
+                    (vector :in)
+                    (db/delete! model# :id)))))))
+
+(expect
+  (try
+    ;; in case it already exists
+    (u/ignore-exceptions
+      (delete-directory! dump-dir))
+    (let [fingerprint (ts/with-world
+                        (dump dump-dir (:email (test-users/fetch-user :crowberto)))
+                        [[Database      (Database db-id)]
+                         [Table         (Table table-id)]
+                         [Field         (Field numeric-field-id)]
+                         [Field         (Field category-field-id)]
+                         [Collection    (Collection collection-id)]
+                         [Collection    (Collection collection-id-nested)]
+                         [Metric        (Metric metric-id)]
+                         [Segment       (Segment segment-id)]
+                         [Dashboard     (Dashboard dashboard-id)]
+                         [Card          (Card card-id)]
+                         [Card          (Card card-id-root)]
+                         [Card          (Card card-id-nested)]
+                         [DashboardCard (DashboardCard dashcard-id)]])]
+      (with-world-cleanup
+        (load dump-dir {:on-error :abort :mode :skip})
+        (every? (fn [[model entity]]
+                  (or (-> entity :name nil?)
+                      (db/select-one model :name (:name entity))))
+                fingerprint)))
+    (finally
+      (delete-directory! dump-dir))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/names_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/names_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..0c3d41b1dd8cbf12fc7e989bc79233866281b82c
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/serialization/names_test.clj
@@ -0,0 +1,68 @@
+(ns metabase-enterprise.serialization.names-test
+  (:require [expectations :refer :all]
+            [metabase
+             [models :refer [Card Collection Dashboard Database Field Metric Segment Table]]
+             [util :as u]]
+            [metabase-enterprise.serialization
+             [names :as names :refer :all]
+             [test-util :as ts]]))
+
+(expect
+  (= (safe-name {:name "foo"}) "foo"))
+(expect
+  (= (safe-name {:name "foo/bar baz"}) "foo%2Fbar baz"))
+
+(expect
+  (= (unescape-name "foo") "foo"))
+(expect
+  (= (unescape-name "foo%2Fbar baz") "foo/bar baz"))
+
+(expect
+  (let [n "foo/bar baz"]
+    (= (-> {:name n} safe-name unescape-name (= n)))))
+
+(defn- test-fully-qualified-name-roundtrip
+  [entity]
+  (let [context (fully-qualified-name->context (fully-qualified-name entity))]
+    (= (u/get-id entity) ((some-fn :field :metric :segment :card :dashboard :collection :table :database) context))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Card card-id-root))))
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Card card-id))))
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Card card-id-nested))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Table table-id))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Field category-field-id))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Metric metric-id))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Segment segment-id))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Collection collection-id))))
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Collection collection-id-nested))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Dashboard dashboard-id))))
+
+(expect
+  (ts/with-world
+    (test-fully-qualified-name-roundtrip (Database db-id))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/serialize_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/serialize_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..ce527d108dca0348c681584b5fb87ee2fc24963e
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/serialization/serialize_test.clj
@@ -0,0 +1,43 @@
+(ns metabase-enterprise.serialization.serialize-test
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
+            [metabase-enterprise.serialization
+             [serialize :as serialize]
+             [test-util :as ts]]
+            [metabase.models :refer [Card Collection Dashboard Database Field Metric Segment Table]]))
+
+(defn- all-ids-are-fully-qualified-names?
+  [m]
+  (every? string? (for [[k v] m
+                        :when (and v (-> k name (str/ends-with? "_id")))]
+                    v)))
+
+(defn- all-mbql-ids-are-fully-qualified-names?
+  [[_ & ids]]
+  (every? string? ids))
+
+(defn- valid-serialization?
+  [s]
+  (->> s
+       (tree-seq coll? identity)
+       (filter (some-fn map? #'serialize/mbql-entity-reference?))
+       (every? (fn [x]
+                 (if (map? x)
+                   (all-ids-are-fully-qualified-names? x)
+                   (all-mbql-ids-are-fully-qualified-names? x))))))
+
+(deftest serialization-test
+  (ts/with-world
+    (letfn [(test-serialization [model id]
+              (testing (name model)
+                (is (valid-serialization? (serialize/serialize (model id))))))]
+      (doseq [[model id] [[Card card-id]
+                          [Metric metric-id]
+                          [Segment segment-id]
+                          [Collection collection-id]
+                          [Dashboard dashboard-id]
+                          [Table table-id]
+                          [Field numeric-field-id]
+                          [Database db-id]]]
+        (test-serialization model id)))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj b/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj
new file mode 100644
index 0000000000000000000000000000000000000000..6ef1a419f0f681724775c4655a24ea3a121a014d
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj
@@ -0,0 +1,75 @@
+(ns metabase-enterprise.serialization.test-util
+  (:require [metabase-enterprise.serialization.names :as names]
+            [metabase.models :refer [Card Collection Dashboard DashboardCard DashboardCardSeries Database Field Metric
+                                     Segment Table]]
+            [metabase.test.data :as data]
+            [toucan.util.test :as tt]))
+
+(defmacro with-world
+  "Run test in the context of a minimal Metabase instance connected to our test database."
+  [& body]
+  `(tt/with-temp* [Database   [{~'db-id :id} (into {} (-> (data/id)
+                                                          Database
+                                                          (dissoc :id :features :name)))]
+                   Table      [{~'table-id :id} (-> (data/id :venues)
+                                                    Table
+                                                    (dissoc :id)
+                                                    (assoc :db_id ~'db-id))]
+                   Field      [{~'numeric-field-id :id} (-> (data/id :venues :price)
+                                                            Field
+                                                            (dissoc :id)
+                                                            (assoc :table_id ~'table-id))]
+                   Field      [{~'category-field-id :id} (-> (data/id :venues :category_id)
+                                                             Field
+                                                             (dissoc :id)
+                                                             (assoc :table_id ~'table-id))]
+                   Collection [{~'collection-id :id} {:name "My Collection"}]
+                   Collection [{~'collection-id-nested :id} {:name "My Nested Collection"
+                                                             :location (format "/%s/" ~'collection-id)}]
+                   Metric     [{~'metric-id :id} {:name "My Metric"
+                                                  :table_id ~'table-id
+                                                  :definition {:source-table ~'table-id
+                                                               :aggregation [:sum [:field-id ~'numeric-field-id]]}}]
+
+                   Segment    [{~'segment-id :id} {:name "My Segment"
+                                                   :table_id ~'table-id
+                                                   :definition {:source-table ~'table-id
+                                                                :filter [:!= [:field-id ~'category-field-id] nil]}}]
+                   Dashboard  [{~'dashboard-id :id} {:name "My Dashboard"
+                                                     :collection_id ~'collection-id}]
+                   Card       [{~'card-id :id}
+                               {:table_id ~'table-id
+                                :name "My Card"
+                                :collection_id ~'collection-id
+                                :dataset_query {:type :query
+                                                :database ~'db-id
+                                                :query {:source-table ~'table-id
+                                                        :aggregation [:sum [:field-id ~'numeric-field-id]]
+                                                        :breakout [[:field-id ~'category-field-id]]}}}]
+                   Card       [{~'card-id-root :id}
+                               {:table_id ~'table-id
+                                ;; https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
+                                :name "My Root Card \\ with a/nasty: (*) //n`me ' * ? \" < > | ŠĐž"
+                                :dataset_query {:type :query
+                                                :database ~'db-id
+                                                :query {:source-table ~'table-id}}}]
+                   Card       [{~'card-id-nested :id}
+                               {:table_id ~'table-id
+                                :name "My Nested Card"
+                                :collection_id ~'collection-id
+                                :dataset_query {:type :query
+                                                :database ~'db-id
+                                                :query {:source-table (str "card__" ~'card-id)}}}]
+                   DashboardCard       [{~'dashcard-id :id}
+                                        {:dashboard_id ~'dashboard-id
+                                         :card_id ~'card-id}]
+                   DashboardCardSeries [~'_ {:dashboardcard_id ~'dashcard-id
+                                             :card_id ~'card-id
+                                             :position 0}]
+                   DashboardCardSeries [~'_ {:dashboardcard_id ~'dashcard-id
+                                             :card_id ~'card-id-nested
+                                             :position 1}]]
+     ~@body))
+
+;; Don't memoize as IDs change in each `with-world` context
+(alter-var-root #'names/path->context (fn [_] #'names/path->context*))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..6d7c3252c93aaa5bc5c62165dfa0acf70be8d2a6
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/serialization/upsert_test.clj
@@ -0,0 +1,99 @@
+(ns metabase-enterprise.serialization.upsert-test
+  (:require [clojure
+             [data :as diff]
+             [test :refer :all]]
+            [metabase
+             [models :refer [Card Collection Dashboard Database Field Metric Pulse Segment Table User]]
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.serialization.upsert :as upsert]
+            [toucan.db :as db]))
+
+(def ^:private same? (comp nil? second diff/diff))
+
+(defn- mutate
+  [model e]
+  (let [identity-constituent? (set (#'upsert/identity-condition model))]
+    (into {} (for [[k v] e]
+               [k (if (or (not (string? v))
+                         (identity-constituent? k))
+                    v
+                    (str (gensym)))]))))
+
+(def ^:private cards
+  (delay [{:name                   "My Card 1"
+           :table_id               (mt/id :venues)
+           :display                :line
+           :creator_id             (mt/user->id :rasta)
+           :visualization_settings {}
+           :dataset_query          {:query    {:source-table (mt/id :venues)}
+                                    :type     :query
+                                    :database (mt/id)}}
+          {:name                   "My Card 2"
+           :table_id               (mt/id :venues)
+           :display                :line
+           :creator_id             (mt/user->id :rasta)
+           :visualization_settings {}
+           :dataset_query          {:query    {:source-table (mt/id :venues)}
+                                    :type     :query
+                                    :database (mt/id)}}]))
+
+;; TODO -- I'm not really clear on what these are testing, so I wasn't sure what to name them when I converted them to
+;; the new style. Feel free to give them better names - Cam
+(deftest maybe-upsert-many!-skip-test
+  (mt/with-model-cleanup [Card]
+    (let [existing-ids (mapv (comp u/get-id (partial db/insert! Card)) @cards)
+          inserted-ids (vec (upsert/maybe-upsert-many! {:mode :skip} Card @cards))]
+      (is (= existing-ids inserted-ids)))))
+
+(deftest maybe-upsert-many!-same-objects-test
+  (mt/with-model-cleanup [Card]
+    (letfn [(test-mode [mode]
+              (testing (format "Mode = %s" mode)
+                (let [[e1 e2]   @cards
+                      [id1 id2] (upsert/maybe-upsert-many! {:mode mode} Card @cards)]
+                  (is (every? (partial apply same?)
+                              [[(Card id1) e1] [(Card id2) e2]])))))]
+      (doseq [mode [:skip :update]]
+        (test-mode mode)))))
+
+(deftest maybe-upsert-many!-update-test
+  (mt/with-model-cleanup [Card]
+    (let [[e1 e2]           @cards
+          id1               (u/get-id (db/insert! Card e1))
+          e1-mutated        (mutate Card e1)
+          [id1-mutated id2] (upsert/maybe-upsert-many! {:mode :update} Card [e1-mutated e2])]
+      (testing "Card 1 ID"
+        (is (= id1 id1-mutated)))
+      (testing "Card 1"
+        (is (same? (Card id1-mutated) e1-mutated)))
+      (testing "Card 2"
+        (is (same? (Card id2) e2))))))
+
+(deftest identical-test
+  (letfn [(test-select-identical [model]
+            (testing (name model)
+              (let [[e1 e2] (if (contains? (set (#'upsert/identity-condition model)) :name)
+                              [{:name "a"} {:name "b"}]
+                              [{} {}])]
+                (mt/with-temp* [model [_ e1] ; create an additional entity so we're sure whe get the right one
+                                model [{id :id} e2]]
+                  (let [e (model id)]
+                    (is (= (#'upsert/select-identical model e) e)))))))]
+    (doseq [model [Collection
+                   Card
+                   Table
+                   Field
+                   Metric
+                   Segment
+                   Dashboard
+                   Database
+                   Pulse
+                   User]]
+      (test-select-identical model))))
+
+(deftest has-post-insert?-test
+  (is (= true
+         (#'upsert/has-post-insert? User)))
+  (is (= false
+         (#'upsert/has-post-insert? Table))))
diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..4e305c86761eca589edf9b171d76b618ea20ec67
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj
@@ -0,0 +1,194 @@
+(ns metabase-enterprise.sso.integrations.jwt-test
+  (:require [buddy.sign
+             [jwt :as jwt]
+             [util :as buddy-util]]
+            [clojure
+             [string :as str]
+             [test :refer :all]]
+            [crypto.random :as crypto-random]
+            [metabase
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sso.integrations
+             [jwt :as mt.jwt]
+             [saml-test :as saml-test]]
+            [metabase.models
+             [permissions-group :as group :refer [PermissionsGroup]]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]
+             [user :refer [User]]]
+            [metabase.public-settings.metastore-test :as metastore-test]
+            [metabase.test.fixtures :as fixtures]
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
+
+(use-fixtures :once (fixtures/initialize :test-users))
+
+(defn- disable-other-sso-types [thunk]
+  (mt/with-temporary-setting-values [ldap-enabled false
+                                     saml-enabled false]
+    (thunk)))
+
+(use-fixtures :each disable-other-sso-types)
+
+(def ^:private default-idp-uri      "http://test.idp.metabase.com")
+(def ^:private default-redirect-uri "http://localhost:3000/test")
+(def ^:private default-jwt-secret   (crypto-random/hex 32))
+
+(deftest sso-prereqs-test
+  (testing "SSO requests fail if SAML hasn't been enabled"
+    (mt/with-temporary-setting-values [jwt-enabled false]
+      (saml-test/with-valid-metastore-token
+        (is (= "SSO has not been enabled and/or configured"
+               (saml-test/client :get 400 "/auth/sso"))))
+
+      (testing "SSO requests fail if they don't have a valid metastore token"
+        (metastore-test/with-metastore-token-features nil
+          (is (= "SSO requires a valid token"
+                 (saml-test/client :get 403 "/auth/sso")))))))
+
+  (testing "SSO requests fail if SAML is enabled but hasn't been configured"
+    (saml-test/with-valid-metastore-token
+      (mt/with-temporary-setting-values [jwt-enabled true]
+        (is (= "JWT SSO has not been enabled and/or configured"
+               (saml-test/client :get 400 "/auth/sso"))))))
+
+  (testing "The IdP provider certificate must also be included for SSO to be configured"
+    (saml-test/with-valid-metastore-token
+      (mt/with-temporary-setting-values [jwt-enabled               true
+                                         jwt-identity-provider-uri default-idp-uri]
+        (is (= "JWT SSO has not been enabled and/or configured"
+               (saml-test/client :get 400 "/auth/sso")))))))
+
+(defn- call-with-default-jwt-config [f]
+  (mt/with-temporary-setting-values [jwt-enabled               true
+                                     jwt-identity-provider-uri default-idp-uri
+                                     jwt-shared-secret         default-jwt-secret]
+    (f)))
+
+(defmacro ^:private with-jwt-default-setup [& body]
+  `(disable-other-sso-types
+    (fn []
+      (saml-test/with-valid-metastore-token
+        (saml-test/call-with-login-attributes-cleared!
+         (fn []
+           (call-with-default-jwt-config
+            (fn []
+              ~@body))))))))
+
+(deftest redirect-test
+  (testing "with JWT configured, a GET request should result in a redirect to the IdP"
+    (with-jwt-default-setup
+      (let [result       (saml-test/client-full-response :get 302 "/auth/sso"
+                                                         {:request-options {:redirect-strategy :none}}
+                                                         :redirect default-redirect-uri)
+            redirect-url (get-in result [:headers "Location"])]
+        (is (str/starts-with? redirect-url default-idp-uri))))))
+
+(deftest happy-path-test
+  (testing (str "Happy path login, valid JWT, checks to ensure the user was logged in successfully and the redirect to "
+                "the right location")
+    (with-jwt-default-setup
+      (let [response (saml-test/client-full-response :get 302 "/auth/sso" {:request-options {:redirect-strategy :none}}
+                                                     :return_to default-redirect-uri
+                                                     :jwt (jwt/sign {:email      "rasta@metabase.com"
+                                                                     :first_name "Rasta"
+                                                                     :last_name  "Toucan"
+                                                                     :extra      "keypairs"
+                                                                     :are        "also present"}
+                                                                    default-jwt-secret))]
+        (is (saml-test/successful-login? response))
+        (testing "redirect URI"
+          (is (= default-redirect-uri
+                 (get-in response [:headers "Location"]))))
+        (testing "login attributes"
+          (is (= {"extra" "keypairs", "are" "also present"}
+                 (db/select-one-field :login_attributes User :email "rasta@metabase.com"))))))))
+
+(deftest expired-jwt-test
+  (testing "Check an expired JWT"
+    (with-jwt-default-setup
+      (is (= "Token is older than max-age (180)"
+             (:message (saml-test/client :get 500 "/auth/sso" {:request-options {:redirect-strategy :none}}
+                                         :return_to default-redirect-uri
+                                         :jwt (jwt/sign {:email "test@metabase.com", :first_name "Test" :last_name "User"
+                                                         :iat   (- (buddy-util/now) (u/minutes->seconds 5))}
+                                                        default-jwt-secret))))))))
+
+(defmacro with-users-with-email-deleted {:style/indent 1} [user-email & body]
+  `(try
+     ~@body
+     (finally
+       (db/delete! User :%lower.email (u/lower-case-en ~user-email)))))
+
+(deftest create-new-account-test
+  (testing "A new account will be created for a JWT user we haven't seen before"
+    (with-jwt-default-setup
+      (with-users-with-email-deleted "newuser@metabase.com"
+        (letfn [(new-user-exists? []
+                  (boolean (seq (db/select User :%lower.email "newuser@metabase.com"))))]
+          (is (= false
+                 (new-user-exists?)))
+          (let [response (saml-test/client-full-response :get 302 "/auth/sso"
+                                                         {:request-options {:redirect-strategy :none}}
+                                                         :return_to default-redirect-uri
+                                                         :jwt (jwt/sign {:email      "newuser@metabase.com"
+                                                                         :first_name "New"
+                                                                         :last_name  "User"
+                                                                         :more       "stuff"
+                                                                         :for        "the new user"}
+                                                                        default-jwt-secret))]
+            (is (saml-test/successful-login? response))
+            (testing "new user"
+              (is (= [{:email        "newuser@metabase.com"
+                       :first_name   "New"
+                       :is_qbnewb    true
+                       :is_superuser false
+                       :id           true
+                       :last_name    "User"
+                       :date_joined  true
+                       :common_name  "New User"}]
+                     (->> (mt/boolean-ids-and-timestamps (db/select User :email "newuser@metabase.com"))
+                          (map #(dissoc % :last_login))))))
+            (testing "attributes"
+              (is (= {"more" "stuff"
+                      "for"  "the new user"}
+                     (db/select-one-field :login_attributes User :email "newuser@metabase.com"))))))))))
+
+(deftest group-mappings-test
+  (testing "make sure our setting for mapping group names -> IDs works"
+    (mt/with-temporary-setting-values [jwt-group-mappings {"group_1" [1 2 3]
+                                                           "group_2" [3 4]
+                                                           "group_3" [5]}]
+      (testing "keyword group names"
+        (is (= #{1 2 3 4}
+               (#'mt.jwt/group-names->ids [:group_1 :group_2]))))
+      (testing "string group names"
+        (is (= #{3 4 5}
+               (#'mt.jwt/group-names->ids ["group_2" "group_3"])))))))
+
+(defn- group-memberships [user-or-id]
+  (when-let [group-ids (seq (db/select-field :group_id PermissionsGroupMembership :user_id (u/get-id user-or-id)))]
+    (db/select-field :name PermissionsGroup :id [:in group-ids])))
+
+(deftest login-sync-group-memberships-test
+  (testing "login should sync group memberships if enabled"
+    (with-jwt-default-setup
+      (tt/with-temp PermissionsGroup [my-group {:name (str ::my-group)}]
+        (mt/with-temporary-setting-values [jwt-group-sync       true
+                                           jwt-group-mappings   {"my_group" [(u/get-id my-group)]}
+                                           jwt-attribute-groups "GrOuPs"]
+          (with-users-with-email-deleted "newuser@metabase.com"
+            (let [response    (saml-test/client-full-response :get 302 "/auth/sso"
+                                                              {:request-options {:redirect-strategy :none}}
+                                                              :return_to default-redirect-uri
+                                                              :jwt (jwt/sign {:email      "newuser@metabase.com"
+                                                                              :first_name "New"
+                                                                              :last_name  "User"
+                                                                              :more       "stuff"
+                                                                              :GrOuPs     ["my_group"]
+                                                                              :for        "the new user"}
+                                                                             default-jwt-secret))]
+              (is (saml-test/successful-login? response))
+              (is (= #{"All Users"
+                       ":metabase-enterprise.sso.integrations.jwt-test/my-group"}
+                     (group-memberships (u/get-id (db/select-one-id User :email "newuser@metabase.com"))))))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..50400b82fdba1af5dbeb9232ae43844f96ad0509
--- /dev/null
+++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj
@@ -0,0 +1,486 @@
+(ns metabase-enterprise.sso.integrations.saml-test
+  (:require [clojure
+             [set :as set]
+             [string :as str]
+             [test :refer :all]]
+            [metabase
+             [config :as config]
+             [http-client :as http]
+             [public-settings :as public-settings]
+             [test :as mt]
+             [util :as u]]
+            [metabase-enterprise.sso.integrations.sso-settings :as sso-settings]
+            [metabase.middleware.session :as mw.session]
+            [metabase.models
+             [permissions-group :as group :refer [PermissionsGroup]]
+             [permissions-group-membership :refer [PermissionsGroupMembership]]
+             [user :refer [User]]]
+            [metabase.public-settings.metastore-test :as metastore-test]
+            [metabase.test
+             [fixtures :as fixtures]
+             [util :as tu]]
+            [ring.util.codec :as codec]
+            [saml20-clj
+             [core :as saml20]
+             [encode-decode :as encode-decode]]
+            [toucan.db :as db]
+            [toucan.util.test :as tt])
+  (:import java.net.URL
+           java.nio.charset.StandardCharsets
+           org.apache.http.client.utils.URLEncodedUtils
+           org.apache.http.message.BasicNameValuePair))
+
+(use-fixtures :once (fixtures/initialize :test-users))
+
+(defn- disable-other-sso-types [thunk]
+  (mt/with-temporary-setting-values [ldap-enabled false
+                                     jwt-enabled  false]
+    (thunk)))
+
+(use-fixtures :each disable-other-sso-types)
+
+(defmacro with-valid-metastore-token
+  "Stubs the `metastore/enable-sso?` function to simulate a valid token. This needs to be included to test any of the
+  SSO features"
+  [& body]
+  `(metastore-test/with-metastore-token-features #{:sso}
+     ~@body))
+
+(defn client
+  "Same as `http/client` but doesn't include the `/api` in the URL prefix"
+  [& args]
+  (binding [http/*url-prefix* (str "http://localhost:" (config/config-str :mb-jetty-port))]
+    (apply http/client args)))
+
+(defn client-full-response
+  "Same as `http/client-full-response` but doesn't include the `/api` in the URL prefix"
+  [& args]
+  (binding [http/*url-prefix* (str "http://localhost:" (config/config-str :mb-jetty-port))]
+    (apply http/client-full-response args)))
+
+(defn successful-login?
+  "Return true if the response indicates a successful user login"
+  [resp]
+  (string? (get-in resp [:cookies @#'mw.session/metabase-session-cookie :value])))
+
+(def ^:private default-idp-uri            "http://test.idp.metabase.com")
+(def ^:private default-redirect-uri       "http://localhost:3000/test")
+(def ^:private default-idp-uri-with-param (str default-idp-uri "?someparam=true"))
+
+(def ^:private default-idp-cert
+  "Public certificate from Auth0, used to validate mock SAML responses from Auth0"
+  "MIIDEzCCAfugAwIBAgIJYpjQiNMYxf1GMA0GCSqGSIb3DQEBCwUAMCcxJTAjBgNV
+BAMTHHNhbWwtbWV0YWJhc2UtdGVzdC5hdXRoMC5jb20wHhcNMTgwNTI5MjEwMDIz
+WhcNMzIwMjA1MjEwMDIzWjAnMSUwIwYDVQQDExxzYW1sLW1ldGFiYXNlLXRlc3Qu
+YXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzNcrpju4
+sILZQNe1adwg3beXtAMFGB+Buuc414+FDv2OG7X7b9OSYar/nsYfWwiazZRxEGri
+agd0Sj5mJ4Qqx+zmB/r4UgX3q/KgocRLlShvvz5gTD99hR7LonDPSWET1E9PD4XE
+1fRaq+BwftFBl45pKTcCR9QrUAFZJ2R/3g06NPZdhe4bg/lTssY5emCxaZpQEku/
+v+zzpV2nLF4by0vSj7AHsubrsLgsCfV3JvJyTxCyo1aIOlv4Vrx7h9rOgl9eEmoU
+5XJAl3D7DuvSTEOy7MyDnKF17m7l5nOPZCVOSzmCWvxSCyysijgsM5DSgAE8DPJy
+oYezV3gTX2OO2QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSp
+B3lvrtbSDuXkB6fhbjeUpFmL2DAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQEL
+BQADggEBAAEHGIAhR5GPD2JxgLtpNtZMCYiAM4Gr7hoTQMaKiXgVtdQu4iMFfbpE
+wIr6UVaDU2HKhvSRFIilOjRGmCGrIzvJgR2l+RL1Z3KrZypI1AXKJT5pF5g5FitB
+sZq+kiUpdRILl2hICzw9Q1M2Le+JSUcHcbHTVgF24xuzOZonxeE56Oc26Ju4CorL
+pM3Nb5iYaGOlQ+48/GP82cLxlVyi02va8tp7KP03ePSaZeBEKGpFtBtEN/dC3NKO
+1mmrT9284H0tvete6KLUH+dsS6bDEYGHZM5KGoSLWRr3qYlCB3AmAw+KvuiuSczL
+g9oYBkdxlhK9zZvkjCgaLCen+0aY67A=")
+
+(defn- do-with-some-validators-disabled
+  "The sample responses all have `InResponseTo=\"_1\"` and invalid assertion signatures (they were edited by hand) so
+  manually add `_1` to the state manager and turn off the <Assertion> signature validator so we can actually run
+  tests."
+  {:style/indent [:defn 2]}
+  ([f]
+   (do-with-some-validators-disabled nil #{:signature :not-on-or-after :recipient :issuer}
+     f))
+
+  ([disabled-response-validators disabled-assertion-validators f]
+   (let [orig              saml20/validate
+         remove-validators (fn [options]
+                             (-> options
+                                 (update :response-validators #(set/difference (set %) (set disabled-response-validators)))
+                                 (update :assertion-validators #(set/difference (set %) (set disabled-assertion-validators)))))]
+     (with-redefs [saml20/validate (fn f
+                                     ([response idp-cert sp-private-key]
+                                      (f response idp-cert sp-private-key saml20/default-validation-options))
+                                     ([response idp-cert sp-private-key options]
+                                      (let [options (merge saml20/default-validation-options options)]
+                                        (orig response idp-cert sp-private-key (remove-validators options)))))]
+       (f)))))
+
+(deftest validate-certificate-test
+  (testing "make sure our test certificate is actually valid"
+    (is (some? (#'sso-settings/validate-saml-idp-cert default-idp-cert)))))
+
+(deftest require-valid-metastore-token-test
+  (testing "SSO requests fail if they don't have a valid metastore token"
+    (metastore-test/with-metastore-token-features #{}
+      (is (= "SSO requires a valid token"
+             (client :get 403 "/auth/sso"))))))
+
+(deftest require-saml-enabled-test
+  (testing "SSO requests fail if SAML hasn't been enabled"
+    (with-valid-metastore-token
+      (mt/with-temporary-setting-values [saml-enabled false]
+        (is (some? (client :get 400 "/auth/sso"))))))
+
+  (testing "SSO requests fail if SAML is enabled but hasn't been configured"
+    (with-valid-metastore-token
+      (tu/with-temporary-setting-values [saml-enabled               true
+                                         saml-identity-provider-uri nil]
+        (is (some? (client :get 400 "/auth/sso"))))))
+
+  (testing "The IDP provider certificate must also be included for SSO to be configured"
+    (with-valid-metastore-token
+      (tu/with-temporary-setting-values [saml-enabled                       true
+                                         saml-identity-provider-uri         default-idp-uri
+                                         saml-identity-provider-certificate nil]
+        (is (some? (client :get 400 "/auth/sso")))))))
+
+;;
+;; The basic flow of of a SAML login is below
+;;
+;; 1. User attempts to access <URL> but is not authenticated
+;; 2. User is redirected to GET /auth/sso
+;; 3. Metabase issues another redirect to the identity provider URI
+;; 4. User logs into their identity provider (i.e. Auth0)
+;; 5. Identity provider POSTs to Metabase with successful auth info
+;; 6. Metabase parses/validates the SAML response
+;; 7. Metabase inits the user session, responds with a redirect to back to the original <URL>
+;;
+
+(defn- call-with-default-saml-config [f]
+  (tu/with-temporary-setting-values [saml-enabled                       true
+                                     saml-identity-provider-uri         default-idp-uri
+                                     saml-identity-provider-certificate default-idp-cert]
+    (f)))
+
+(defn call-with-login-attributes-cleared!
+  "If login_attributes remain after these tests run, depending on the order that the tests run, lots of tests will
+  fail as the login_attributes data from this tests is unexpected in those other tests"
+  [f]
+  (try
+    (f)
+    (finally
+      (u/ignore-exceptions (db/update-where! User {} :login_attributes nil)))))
+
+(defmacro ^:private with-saml-default-setup [& body]
+  `(with-valid-metastore-token
+     (call-with-login-attributes-cleared!
+      (fn []
+        (call-with-default-saml-config
+         (fn []
+           ~@body))))))
+
+(deftest request-xml-test
+  (testing "Make sure the requests we generate look correct"
+    (with-saml-default-setup
+      (mt/with-temporary-setting-values [site-url "http://localhost:3000"]
+        (let [orig saml20/request]
+          (with-redefs [saml20/request (fn [m]
+                                         (testing "Request ID should be of the format id-<uuid>"
+                                           (is (re= (re-pattern (str "^id-" u/uuid-regex "$"))
+                                                    (:request-id m))))
+                                         (mt/with-clock #t "2020-09-30T17:53:32Z"
+                                           (orig (assoc m :request-id "id-419507d5-1d2a-43c4-bcde-3e5b9746bb47"))))]
+            (let [request     (client-full-response :get 302 "/auth/sso"
+                                                    {:request-options {:redirect-strategy :none}}
+                                                    :redirect default-redirect-uri)
+                  location    (get-in request [:headers "Location"])
+                  [_ base-64] (re-find #"SAMLRequest=([^&]+)" location)
+                  xml         (-> base-64
+                                  codec/url-decode
+                                  encode-decode/base64->inflate->str
+                                  (str/replace #"\n+" "")
+                                  (str/replace #">\s+<" "><"))]
+              (is (= (str "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+                          "<samlp:AuthnRequest"
+                          " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\""
+                          " AssertionConsumerServiceURL=\"http://localhost:3000/auth/sso\""
+                          " Destination=\"http://test.idp.metabase.com\""
+                          " ID=\"id-419507d5-1d2a-43c4-bcde-3e5b9746bb47\""
+                          " IssueInstant=\"2020-09-30T17:53:32Z\""
+                          " ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\""
+                          " ProviderName=\"Metabase\""
+                          " Version=\"2.0\">"
+                          "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">Metabase</saml:Issuer>"
+                          "</samlp:AuthnRequest>")
+                     xml)))))))))
+
+(deftest redirect-test
+  (testing "With SAML configured, a GET request should result in a redirect to the IDP"
+    (with-saml-default-setup
+      (let [result       (client-full-response :get 302 "/auth/sso"
+                                               {:request-options {:redirect-strategy :none}}
+                                               :redirect default-redirect-uri)
+            redirect-url (get-in result [:headers "Location"])]
+        (is (str/starts-with? redirect-url default-idp-uri))))))
+
+;; TODO - maybe this belongs in a util namespace?
+(defn- uri->params-map
+  "Parse the URI string, creating a map from the key/value pairs in the query string"
+  [uri-str]
+  (assert (string? uri-str))
+  (into
+   {}
+   (for [^BasicNameValuePair pair (-> (URL. uri-str) .getQuery (URLEncodedUtils/parse StandardCharsets/UTF_8))]
+     [(keyword (.getName pair)) (.getValue pair)])))
+
+(deftest uri->params-map-test
+  (is (= {:a "b", :c "d"}
+         (uri->params-map "http://localhost?a=b&c=d"))))
+
+(deftest redirect-append-paramters-test
+  (testing (str "When the identity provider already includes a query parameter, the SAML code should spot that and "
+                "append more parameters onto the query string (rather than always include a `?newparam=here`).")
+    (with-saml-default-setup
+      (tu/with-temporary-setting-values [saml-identity-provider-uri default-idp-uri-with-param]
+        (let [result       (client-full-response :get 302 "/auth/sso"
+                                                 {:request-options {:redirect-strategy :none}}
+                                                 :redirect default-redirect-uri)
+              redirect-url (get-in result [:headers "Location"])]
+          (is (= #{:someparam :SAMLRequest :RelayState}
+                 (set (keys (uri->params-map redirect-url))))))))))
+
+;; The RelayState is data we include in the redirect request to the IDP. The IDP will include the RelayState in it's
+;; response via the POST. This allows the FE to track what the original route the user was trying to access was and
+;; redirect the user back to that original URL after successful authentication
+(deftest relay-state-test
+  (with-saml-default-setup
+    (do-with-some-validators-disabled
+     (fn []
+       (let [result       (client-full-response :get 302 "/auth/sso"
+                                                {:request-options {:redirect-strategy :none}}
+                                                :redirect default-redirect-uri)
+             redirect-url (get-in result [:headers "Location"])]
+         (testing (format "result = %s" (pr-str result))
+           (is (string? redirect-url))
+           (is (= default-redirect-uri
+                  (saml20/base64->str (:RelayState (uri->params-map redirect-url)))))))))))
+
+(defn- saml-response-from-file [filename]
+  (u/encode-base64 (slurp filename)))
+
+(defn- saml-test-response []
+  (saml-response-from-file "test_resources/saml-test-response.xml"))
+
+(defn- new-user-saml-test-response []
+  (saml-response-from-file "test_resources/saml-test-response-new-user.xml"))
+
+(defn- new-user-with-single-group-saml-test-response []
+  (saml-response-from-file "test_resources/saml-test-response-new-user-with-single-group.xml"))
+
+(defn- new-user-with-groups-saml-test-response []
+  (saml-response-from-file "test_resources/saml-test-response-new-user-with-groups.xml"))
+
+(defn- saml-post-request-options [saml-response relay-state]
+  {:request-options {:content-type     :x-www-form-urlencoded
+                     :redirect-strategy :none
+                     :form-params      {:SAMLResponse saml-response
+                                        :RelayState   relay-state}}})
+
+(defn- some-saml-attributes [user-nickname]
+  {"http://schemas.auth0.com/identities/default/provider"   "auth0"
+   "http://schemas.auth0.com/nickname"                      user-nickname
+   "http://schemas.auth0.com/identities/default/connection" "Username-Password-Authentication"})
+
+(defn- saml-login-attributes [email]
+  (let [attribute-keys (keys (some-saml-attributes nil))]
+    (-> (db/select-one-field :login_attributes User :email email)
+        (select-keys attribute-keys))))
+
+(deftest validate-request-id-test
+  (testing "Sample response shoudl fail because _1 isn't a request ID that we issued."
+    (with-saml-default-setup
+      (do-with-some-validators-disabled
+       (fn []
+         (testing (str "After a successful login with the identity provider, the SAML provider will POST to the "
+                       "`/auth/sso` route.")
+           (let [req-options (saml-post-request-options (saml-test-response)
+                                                        (saml20/str->base64 default-redirect-uri))
+                 response    (client-full-response :post 302 "/auth/sso" req-options)]
+             (is (successful-login? response))
+             (is (= default-redirect-uri
+                    (get-in response [:headers "Location"])))
+             (is (= (some-saml-attributes "rasta")
+                    (saml-login-attributes "rasta@metabase.com"))))))))))
+
+(deftest validate-signatures-test
+  ;; they were edited by hand I think, so the signatures are now incorrect (?)
+  (testing "The sample responses should normally fail because the <Assertion> signatures don't match"
+    (with-saml-default-setup
+      (do-with-some-validators-disabled nil #{:not-on-or-after :recipient :issuer}
+       (fn []
+         (let [req-options (saml-post-request-options (saml-test-response)
+                                                      default-redirect-uri)
+               response    (client-full-response :post 401 "/auth/sso" req-options)]
+           (testing (format "response =\n%s" (u/pprint-to-str response))
+             (is (not (successful-login? response))))))))))
+
+(deftest validate-not-on-or-after-test
+  (with-saml-default-setup
+    (testing "The sample responses should normally fail because the <Assertion> NotOnOrAfter has passed"
+      (do-with-some-validators-disabled nil #{:signature :recipient}
+       (fn []
+         (let [req-options (saml-post-request-options (saml-test-response)
+                                                      (saml20/str->base64 default-redirect-uri))]
+           (is (not (successful-login? (client-full-response :post 401 "/auth/sso" req-options))))))))
+    (testing "If we time-travel then the sample responses *should* work"
+      (let [orig saml20/validate]
+        (with-redefs [saml20/validate (fn [& args]
+                                        (mt/with-clock #t "2018-07-01T00:00:00.000Z"
+                                          (apply orig args)))]
+          (do-with-some-validators-disabled nil #{:signature :recipient :issuer}
+           (fn []
+             (let [req-options (saml-post-request-options (saml-test-response)
+                                                          (saml20/str->base64 default-redirect-uri))]
+               (is (successful-login? (client-full-response :post 302 "/auth/sso" req-options)))))))))))
+
+(deftest validate-recipient-test
+  (with-saml-default-setup
+    (testing (str "The sample responses all have <Recipient> of localhost:3000. "
+                  "If (site-url) is set to something different, this should fail.")
+      (do-with-some-validators-disabled nil #{:signature :not-on-or-after :issuer}
+        (fn []
+          (testing "with incorrect acs-url"
+            (mt/with-temporary-setting-values [site-url "http://localhost:9876"]
+              (let [req-options (saml-post-request-options (saml-test-response)
+                                                           (saml20/str->base64 default-redirect-uri))]
+                (is (not (successful-login? (client-full-response :post 401 "/auth/sso" req-options)))))))
+          (testing "with correct acs-url"
+            (mt/with-temporary-setting-values [site-url "http://localhost:3000"]
+              (let [req-options (saml-post-request-options (saml-test-response)
+                                                           (saml20/str->base64 default-redirect-uri))]
+                (is (successful-login? (client-full-response :post 302 "/auth/sso" req-options)))))))))))
+
+(deftest validate-issuer-test
+  (with-saml-default-setup
+    (testing "If the `saml-identity-provider-issuer` Setting is set, we should validate <Issuer> in Responses"
+      (do-with-some-validators-disabled nil #{:signature :not-on-or-after :recipient}
+        (letfn [(login [expected-status-code]
+                  (let [req-options (saml-post-request-options (saml-test-response)
+                                                               (saml20/str->base64 default-redirect-uri))]
+                    (client-full-response :post expected-status-code "/auth/sso" req-options)))]
+          (fn []
+            (testing "<Issuer> matches saml-identity-provider-issuer"
+              (mt/with-temporary-setting-values [saml-identity-provider-issuer "urn:saml-metabase-test.auth0.com"]
+                (is (successful-login? (login 302)))))
+            (testing "<Issuer> does not match saml-identity-provider-issuer"
+              (mt/with-temporary-setting-values [saml-identity-provider-issuer "WRONG"]
+                (is (not (successful-login? (login 401))))))
+            (testing "saml-identity-provider-issuer is not set: shouldn't do any validation"
+              (mt/with-temporary-setting-values [saml-identity-provider-issuer nil]
+                (is (successful-login? (login 302)))))))))))
+
+;; Part of accepting the POST is validating the response and the relay state so we can redirect the user to their
+;; original destination
+(deftest login-test
+  (with-saml-default-setup
+    (do-with-some-validators-disabled
+     (fn []
+       (testing "After a successful login with the identity provider, the SAML provider will POST to the `/auth/sso` route."
+         (let [req-options (saml-post-request-options (saml-test-response)
+                                                      (saml20/str->base64 default-redirect-uri))
+               response    (client-full-response :post 302 "/auth/sso" req-options)]
+           (is (successful-login? response))
+           (is (= default-redirect-uri
+                  (get-in response [:headers "Location"])))
+           (is (= (some-saml-attributes "rasta")
+                  (saml-login-attributes "rasta@metabase.com")))))))))
+
+(deftest login-invalid-relay-state-test
+  (testing (str "if the RelayState is not set or is invalid, you are redirected back to the home page rather than "
+                "failing the entire login")
+    (doseq [relay-state ["something-random_#!@__^^"
+                         ""
+                         "   "
+                         "/"]]
+      (testing (format "\nRelayState = %s" (pr-str relay-state))
+        (with-saml-default-setup
+          (do-with-some-validators-disabled
+            (fn []
+              (let [req-options (saml-post-request-options (saml-test-response) relay-state)
+                    response    (client-full-response :post 302 "/auth/sso" req-options)]
+                (is (successful-login? response))
+                (is (= (public-settings/site-url)
+                       (get-in response [:headers "Location"])))
+                (is (= (some-saml-attributes "rasta")
+                       (saml-login-attributes "rasta@metabase.com")))))))))))
+
+(deftest login-create-account-test
+  (testing "A new account will be created for a SAML user we haven't seen before"
+    (do-with-some-validators-disabled
+      (fn []
+        (with-saml-default-setup
+          (try
+            (is (not (db/exists? User :%lower.email "newuser@metabase.com")))
+            (let [req-options (saml-post-request-options (new-user-saml-test-response)
+                                                         (saml20/str->base64 default-redirect-uri))]
+              (is (successful-login? (client-full-response :post 302 "/auth/sso" req-options)))
+              (is (= [{:email        "newuser@metabase.com"
+                       :first_name   "New"
+                       :is_qbnewb    true
+                       :is_superuser false
+                       :id           true
+                       :last_name    "User"
+                       :date_joined  true
+                       :common_name  "New User"}]
+                     (->> (tu/boolean-ids-and-timestamps (db/select User :email "newuser@metabase.com"))
+                          (map #(dissoc % :last_login)))))
+              (is (= (some-saml-attributes "newuser")
+                     (saml-login-attributes "newuser@metabase.com"))))
+            (finally
+              (db/delete! User :%lower.email "newuser@metabase.com"))))))))
+
+(defn- group-memberships [user-or-id]
+  (when-let [group-ids (seq (db/select-field :group_id PermissionsGroupMembership :user_id (u/get-id user-or-id)))]
+    (db/select-field :name PermissionsGroup :id [:in group-ids])))
+
+(deftest login-should-sync-single-group-membership
+  (testing "saml group sync works when there's just a single group, which gets interpreted as a string"
+    (with-saml-default-setup
+      (do-with-some-validators-disabled
+       (fn []
+         (tt/with-temp PermissionsGroup [group-1 {:name (str ::group-1)}]
+           (tu/with-temporary-setting-values [saml-group-sync      true
+                                              saml-group-mappings  {"group_1" [(u/get-id group-1)]}
+                                              saml-attribute-group "GroupMembership"]
+             (try
+               ;; user doesn't exist until SAML request
+               (is (not (db/select-one-id User :%lower.email "newuser@metabase.com")))
+               (let [req-options (saml-post-request-options (new-user-with-single-group-saml-test-response)
+                                                            (saml20/str->base64 default-redirect-uri))
+                     response    (client-full-response :post 302 "/auth/sso" req-options)]
+                 (is (successful-login? response))
+                 (is (= #{"All Users"
+                          ":metabase-enterprise.sso.integrations.saml-test/group-1"}
+                        (group-memberships (db/select-one-id User :email "newuser@metabase.com")))))
+               (finally
+                 (db/delete! User :%lower.email "newuser@metabase.com"))))))))))
+
+(deftest login-should-sync-multiple-group-membership
+  (testing "saml group sync works when there are multiple groups, which gets interpreted as a list of strings"
+    (with-saml-default-setup
+      (do-with-some-validators-disabled
+       (fn []
+         (tt/with-temp* [PermissionsGroup [group-1 {:name (str ::group-1)}]
+                         PermissionsGroup [group-2 {:name (str ::group-2)}]]
+           (tu/with-temporary-setting-values [saml-group-sync      true
+                                              saml-group-mappings  {"group_1" [(u/get-id group-1)]
+                                                                    "group_2" [(u/get-id group-2)]}
+                                              saml-attribute-group "GroupMembership"]
+             (try
+               (testing "user doesn't exist until SAML request"
+                 (is (not (db/select-one-id User :%lower.email "newuser@metabase.com"))))
+               (let [req-options (saml-post-request-options (new-user-with-groups-saml-test-response)
+                                                            (saml20/str->base64 default-redirect-uri))
+                     response    (client-full-response :post 302 "/auth/sso" req-options)]
+                 (is (successful-login? response))
+                 (is (= #{"All Users"
+                          ":metabase-enterprise.sso.integrations.saml-test/group-1"
+                          ":metabase-enterprise.sso.integrations.saml-test/group-2"}
+                        (group-memberships (db/select-one-id User :email "newuser@metabase.com")))))
+               (finally
+                 (db/delete! User :%lower.email "newuser@metabase.com"))))))))))
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/.eslintrc b/enterprise/frontend/src/metabase-enterprise/audit_app/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..8d694c6332a0067789b453fc404847516d129712
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "rules": {
+    "flowtype/require-valid-file-annotation": 1
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditContent.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditContent.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5278e8eb5372678ab1ed7fdc0331f1148c12cf9
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditContent.jsx
@@ -0,0 +1,42 @@
+/* @flow */
+
+import React from "react";
+
+import Radio from "metabase/components/Radio";
+
+export default class AuditContent extends React.Component {
+  render() {
+    const { title, subtitle, tabs, children, location, ...props } = this.props;
+    // HACK: remove the last component to get the base page path. won't work with tabs using IndexRoute (IndexRedirect ok)
+    const pagePath = location && location.pathname.replace(/\/\w+$/, "");
+    return (
+      <div className="py4 flex flex-column flex-full">
+        <div className="px4">
+          <h2 className="PageTitle">{title}</h2>
+          {subtitle && <div className="my1">{subtitle}</div>}
+        </div>
+        {tabs && (
+          <div className="border-bottom px4 mt1">
+            <Radio
+              underlined
+              value={this.props.router.location.pathname}
+              options={tabs.filter(tab => tab.component)} // hide tabs that aren't implemented
+              optionValueFn={tab => `${pagePath}/${tab.path}`}
+              optionNameFn={tab => tab.title}
+              optionKeyFn={tab => tab.path}
+              onChange={this.props.router.push}
+            />
+          </div>
+        )}
+        <div className="px4 full-height">
+          {/* This allows the parent component to inject props into child route components, e.x. userId */}
+          {React.Children.count(children) === 1 &&
+          // NOTE: workaround for https://github.com/facebook/react/issues/12136
+          !Array.isArray(children)
+            ? React.cloneElement(React.Children.only(children), props)
+            : children}
+        </div>
+      </div>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditParameters.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditParameters.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..07ac197a4715dba65f3ceb7f223fea2b240c420f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditParameters.jsx
@@ -0,0 +1,72 @@
+/* @flow */
+
+import React from "react";
+
+import _ from "underscore";
+
+const DEBOUNCE_PERIOD = 300;
+
+type AuditParameter = {
+  key: string,
+  placeholder: string,
+};
+
+type Props = {
+  parameters: AuditParameter[],
+  children?: (committedValues: { [key: string]: string }) => React$Element<any>,
+};
+
+type State = {
+  inputValues: { [key: string]: string },
+  committedValues: { [key: string]: string },
+};
+
+export default class AuditParameters extends React.Component {
+  props: Props;
+  state: State;
+
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      inputValues: {},
+      committedValues: {},
+    };
+  }
+
+  changeValue = (key: string, value: string) => {
+    this.setState({
+      inputValues: { ...this.state.inputValues, [key]: value },
+    });
+    this.commitValueDebounced(key, value);
+  };
+
+  commitValueDebounced = _.debounce((key: string, value: string) => {
+    this.setState({
+      committedValues: { ...this.state.committedValues, [key]: value },
+    });
+  }, DEBOUNCE_PERIOD);
+
+  render() {
+    const { parameters, children } = this.props;
+    const { inputValues, committedValues } = this.state;
+    return (
+      <div>
+        <div className="pt4">
+          {parameters.map(({ key, placeholder }) => (
+            <input
+              className="input"
+              key={key}
+              type="text"
+              value={inputValues[key] || ""}
+              placeholder={placeholder}
+              onChange={e => {
+                this.changeValue(key, e.target.value);
+              }}
+            />
+          ))}
+        </div>
+        {children && children(committedValues)}
+      </div>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditSidebar.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditSidebar.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2980f89dc355d91f583d039b5702112770af49d1
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/components/AuditSidebar.jsx
@@ -0,0 +1,77 @@
+/* @flow */
+
+import React from "react";
+
+import { IndexLink } from "react-router";
+import Link from "metabase/components/Link";
+import cx from "classnames";
+
+type Props = {
+  className?: string,
+  style?: { [key: string]: any },
+  children?: React$Element<any>,
+};
+
+const AuditSidebarSection = ({ title, children }) => (
+  <div className="pb2">
+    {title && <AuditSidebarSectionTitle title={title} />}
+    {children}
+  </div>
+);
+
+const AuditSidebarSectionTitle = ({ title }) => (
+  <div className="py1 text-smaller text-bold text-uppercase text-medium">
+    {title}
+  </div>
+);
+
+const AuditSidebarItem = ({ title, path }) => (
+  <div
+    className={cx("my2 cursor-pointer text-brand-hover", {
+      disabled: !path,
+    })}
+  >
+    {path ? (
+      <Link className="no-decoration" activeClassName="text-brand" to={path}>
+        {title}
+      </Link>
+    ) : (
+      <IndexLink
+        className="no-decoration"
+        activeClassName="text-brand"
+        to="/admin/audit"
+      >
+        {title}
+      </IndexLink>
+    )}
+  </div>
+);
+
+const AuditSidebar = ({ className, style, children }: Props) => (
+  <div style={style} className={cx("p4", className)}>
+    {children}
+  </div>
+);
+
+const AuditAppSidebar = (props: Props) => (
+  <AuditSidebar {...props}>
+    {/* <AuditSidebarSection>
+      <AuditSidebarItem title="Overview" path="/admin/audit/overview" />
+    </AuditSidebarSection> */}
+    <AuditSidebarSection title="People">
+      <AuditSidebarItem title="Team members" path="/admin/audit/members" />
+    </AuditSidebarSection>
+    <AuditSidebarSection title="Data">
+      <AuditSidebarItem title="Databases" path="/admin/audit/databases" />
+      <AuditSidebarItem title="Schemas" path="/admin/audit/schemas" />
+      <AuditSidebarItem title="Tables" path="/admin/audit/tables" />
+    </AuditSidebarSection>
+    <AuditSidebarSection title="Items">
+      <AuditSidebarItem title="Questions" path="/admin/audit/questions" />
+      <AuditSidebarItem title="Dashboards" path="/admin/audit/dashboards" />
+      <AuditSidebarItem title="Downloads" path="/admin/audit/downloads" />
+    </AuditSidebarSection>
+  </AuditSidebar>
+);
+
+export default AuditAppSidebar;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/components/OpenInMetabase.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/components/OpenInMetabase.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..160af6b1b2d08ce1a72b0930c194ba61923a92cd
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/components/OpenInMetabase.jsx
@@ -0,0 +1,19 @@
+/* @flow */
+
+import React from "react";
+
+import Link from "metabase/components/Link";
+import Icon from "metabase/components/Icon";
+
+type Props = {
+  to: string,
+};
+
+const OpenInMetabase = ({ ...props }: Props) => (
+  <Link {...props} className="link flex align-center" target="_blank">
+    <Icon name="external" className="mr1" />
+    Open in Metabase
+  </Link>
+);
+
+export default OpenInMetabase;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/components/SidebarLayoutFixedWidth.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/components/SidebarLayoutFixedWidth.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..83dc731510575b2adb3ff11c5127252bd4a5c40d
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/components/SidebarLayoutFixedWidth.jsx
@@ -0,0 +1,50 @@
+/* eslint "react/prop-types": "warn" */
+import React from "react";
+import PropTypes from "prop-types";
+
+import cx from "classnames";
+
+// SidebarLayoutFixedWidth is similar to SidebarLayout but uses a fixed sidebar
+// width, which is needed for our current Dashboard component to resize correctly
+
+const SidebarLayoutFixedWidth = ({
+  className,
+  style,
+  sidebar,
+  sidebarWidth = 250,
+  right = false,
+  children,
+}) => (
+  <div className={cx("relative", className)} style={style}>
+    {React.cloneElement(
+      sidebar,
+      {
+        className: "Layout-sidebar absolute top left bottom",
+        style: { width: sidebarWidth, ...(sidebar.props.style || {}) },
+      },
+      sidebar.props.children,
+    )}
+    {children &&
+      React.cloneElement(
+        React.Children.only(children),
+        {
+          style: {
+            [right ? "marginRight" : "marginLeft"]: sidebarWidth,
+            ...(React.Children.only(children).props.style || {}),
+          },
+        },
+        React.Children.only(children).props.children,
+      )}
+  </div>
+);
+
+SidebarLayoutFixedWidth.propTypes = {
+  className: PropTypes.string,
+  style: PropTypes.object,
+  sidebar: PropTypes.element.isRequired,
+  children: PropTypes.element.isRequired,
+  sidebarWidth: PropTypes.number,
+  right: PropTypes.bool,
+};
+
+export default SidebarLayoutFixedWidth;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditApp.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditApp.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..edf410c058541540391c8131256132e6c173461a
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditApp.jsx
@@ -0,0 +1,18 @@
+/* @flow */
+
+import React from "react";
+
+import SidebarLayout from "../components/SidebarLayoutFixedWidth";
+import AuditSidebar from "../components/AuditSidebar";
+
+type Props = {
+  children: React$Element<any>,
+};
+
+const AuditApp = ({ children }: Props) => (
+  <SidebarLayout sidebar={<AuditSidebar />}>
+    <div>{children}</div>
+  </SidebarLayout>
+);
+
+export default AuditApp;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditCustomView.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditCustomView.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b83a056e5bdc3c3a1fe410bc329e1921e2bff7ec
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditCustomView.jsx
@@ -0,0 +1,38 @@
+/* @flow */
+
+import React from "react";
+
+import "./AuditTableVisualization";
+
+import QuestionResultLoader from "metabase/containers/QuestionResultLoader";
+
+import Question from "metabase-lib/lib/Question";
+
+import { connect } from "react-redux";
+import { push } from "react-router-redux";
+import { getMetadata } from "metabase/selectors/metadata";
+
+const mapStateToProps = (state, props) => ({
+  metadata: getMetadata(state),
+});
+
+const mapDispatchToProps = {
+  onChangeLocation: push,
+};
+
+@connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)
+export default class AuditTable extends React.Component {
+  render() {
+    const { metadata, card } = this.props;
+    const question = new Question(card.card, metadata);
+
+    return (
+      <QuestionResultLoader className="mt3" question={question}>
+        {this.props.children}
+      </QuestionResultLoader>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditDashboard.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditDashboard.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0361495296040cc9325cf42255180ef91aae92c4
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditDashboard.jsx
@@ -0,0 +1,62 @@
+/* @flow */
+
+import React from "react";
+
+import { connect } from "react-redux";
+import { push } from "react-router-redux";
+import { getMetadata } from "metabase/selectors/metadata";
+
+import { Dashboard } from "metabase/dashboard/containers/Dashboard";
+import DashboardData from "metabase/dashboard/hoc/DashboardData";
+
+const DashboardWithData = DashboardData(Dashboard);
+
+import { AuditMode } from "../lib/util";
+
+import type { AuditCard } from "../types";
+
+import { harmony } from "metabase/lib/colors";
+
+type Props = {
+  cards: AuditCard[],
+};
+
+const AuditDashboard = ({ cards, ...props }: Props) => (
+  <DashboardWithData
+    style={{ backgroundColor: "transparent", padding: 0 }}
+    // HACK: to get inline dashboards working quickly
+    dashboardId={{
+      ordered_cards: cards.map(([{ x, y, w, h }, dc]) => ({
+        col: x,
+        row: y,
+        sizeX: w,
+        sizeY: h,
+        visualization_settings: {
+          // use the legacy "graph.colors" settings with color harmony to force brand color, etc
+          "graph.colors": harmony,
+          // we want to hide the background to help make the charts feel
+          // like they're part of the page, so turn off the background
+          "dashcard.background": false,
+        },
+        ...dc,
+      })),
+    }}
+    mode={AuditMode}
+    // don't link card titles to the query builder
+    noLink
+    {...props}
+  />
+);
+
+const mapStateToProps = (state, props) => ({
+  metadata: getMetadata(state),
+});
+
+const mapDispatchToProps = {
+  onChangeLocation: push,
+};
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)(AuditDashboard);
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTable.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTable.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..60b6bed454fae4125de19b368d0dd39f7cc91d71
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTable.jsx
@@ -0,0 +1,137 @@
+/* @flow */
+
+import React from "react";
+
+import "./AuditTableVisualization";
+
+import QuestionLoadAndDisplay from "./QuestionLoadAndDisplay";
+import Icon from "metabase/components/Icon";
+
+import Question from "metabase-lib/lib/Question";
+
+import { connect } from "react-redux";
+import { push } from "react-router-redux";
+import { getMetadata } from "metabase/selectors/metadata";
+
+import { AuditMode } from "../lib/util";
+
+import { chain } from "icepick";
+import cx from "classnames";
+import { t } from "ttag";
+
+import type { AuditDashCard } from "../types";
+
+type Props = {
+  table: AuditDashCard,
+  pageSize: number,
+};
+type State = {
+  page: number,
+  hasMorePages: boolean,
+};
+
+const mapStateToProps = (state, props) => ({
+  metadata: getMetadata(state),
+});
+
+const mapDispatchToProps = {
+  onChangeLocation: push,
+};
+
+const DEFAULT_PAGE_SIZE = 100;
+
+@connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)
+export default class AuditTable extends React.Component {
+  props: Props;
+  state: State = {
+    page: 0,
+    hasMorePages: false,
+  };
+
+  static defaultProps = {
+    pageSize: DEFAULT_PAGE_SIZE,
+  };
+
+  render() {
+    // $FlowFixMe: metadata, and onChangeLocation provided by @connect
+    const { metadata, table, onChangeLocation, pageSize } = this.props;
+    const { page, hasMorePages } = this.state;
+
+    const card = chain(table.card)
+      .assoc("display", "audit-table")
+      .assocIn(["dataset_query", "limit"], pageSize)
+      .assocIn(["dataset_query", "offset"], pageSize * page)
+      .value();
+
+    const question = new Question(card, metadata);
+
+    return (
+      <div>
+        <QuestionLoadAndDisplay
+          className="mt3"
+          question={question}
+          metadata={metadata}
+          mode={AuditMode}
+          onChangeLocation={onChangeLocation}
+          onChangeCardAndRun={() => {}}
+          onLoad={results =>
+            this.setState({ hasMorePages: results[0].row_count === pageSize })
+          }
+        />
+        {(hasMorePages || page > 0) && (
+          <div className="mt1 pt2 border-top flex">
+            <PaginationControls
+              className="ml-auto"
+              start={page * pageSize}
+              end={(page + 1) * pageSize - 1}
+              hasPrevious={page > 0}
+              hasNext={hasMorePages}
+              onPrevious={() => this.setState({ page: page - 1 })}
+              onNext={() => this.setState({ page: page + 1 })}
+            />
+          </div>
+        )}
+      </div>
+    );
+  }
+}
+
+const PaginationControls = ({
+  className,
+  onNext,
+  onPrevious,
+  hasNext,
+  hasPrevious,
+  start,
+  end,
+  total,
+}) => (
+  <span className={cx(className, "p1 flex flex-no-shrink flex-align-right")}>
+    {start != null && end != null ? (
+      <span className="text-bold">
+        {total
+          ? t`Rows ${start + 1}-${end + 1} of ${total}`
+          : t`Rows ${start + 1}-${end + 1}`}
+      </span>
+    ) : null}
+    <span
+      className={cx("text-brand-hover px1 cursor-pointer", {
+        disabled: !hasPrevious,
+      })}
+      onClick={onPrevious}
+    >
+      <Icon name="left" size={10} />
+    </span>
+    <span
+      className={cx("text-brand-hover pr1 cursor-pointer", {
+        disabled: !hasNext,
+      })}
+      onClick={onNext}
+    >
+      <Icon name="right" size={10} />
+    </span>
+  </span>
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableVisualization.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableVisualization.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b2e088860387edd107a1dbf4f10905aa25e5fa69
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableVisualization.jsx
@@ -0,0 +1,109 @@
+/* @flow */
+
+import React from "react";
+
+import { registerVisualization } from "metabase/visualizations/index";
+
+import { formatColumn, formatValue } from "metabase/lib/formatting";
+import { isColumnRightAligned } from "metabase/visualizations/lib/table";
+
+import Table from "metabase/visualizations/visualizations/Table";
+
+import EmptyState from "metabase/components/EmptyState";
+
+import NoResults from "assets/img/no_results.svg";
+
+import { t } from "ttag";
+
+import _ from "underscore";
+import cx from "classnames";
+
+export default class AuditTableVisualization extends React.Component {
+  static identifier = "audit-table";
+  static noHeader = true;
+  static hidden = true;
+
+  // copy Table's settings and columnSettings
+  static settings = Table.settings;
+  static columnSettings = Table.columnSettings;
+
+  render() {
+    const {
+      series: [
+        {
+          data: { cols, rows },
+        },
+      ],
+      visualizationIsClickable,
+      onVisualizationClick,
+      settings,
+    } = this.props;
+
+    const columnIndexes = settings["table.columns"]
+      .filter(({ enabled }) => enabled)
+      .map(({ name }) => _.findIndex(cols, col => col.name === name));
+
+    if (rows.length === 0) {
+      return (
+        <EmptyState
+          title={t`No results`}
+          illustrationElement={<img src={NoResults} />}
+        />
+      );
+    }
+
+    return (
+      <table className="ContentTable">
+        <thead>
+          <tr>
+            {columnIndexes.map(colIndex => (
+              <th
+                className={cx({
+                  "text-right": isColumnRightAligned(cols[colIndex]),
+                })}
+              >
+                {formatColumn(cols[colIndex])}
+              </th>
+            ))}
+          </tr>
+        </thead>
+        <tbody>
+          {rows.map((row, rowIndex) => (
+            <tr>
+              {columnIndexes.map(colIndex => {
+                const value = row[colIndex];
+                const column = cols[colIndex];
+                const clicked = { column, value, origin: { row, cols } };
+                const clickable = visualizationIsClickable(clicked);
+                const columnSettings = settings.column(column);
+                return (
+                  <td
+                    className={cx({
+                      "text-brand cursor-pointer": clickable,
+                      "text-right": isColumnRightAligned(column),
+                    })}
+                    onClick={
+                      clickable ? () => onVisualizationClick(clicked) : null
+                    }
+                  >
+                    {formatValue(value, {
+                      ...columnSettings,
+                      type: "cell",
+                      jsx: true,
+                      rich: true,
+                      clicked: clicked,
+                      // always show timestamps in local time for the audit app
+                      local: true,
+                    })}
+                  </td>
+                );
+              })}
+            </tr>
+          ))}
+        </tbody>
+      </table>
+    );
+  }
+}
+
+registerVisualization(AuditTableVisualization);
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableWithSearch.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableWithSearch.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..25f7263faa3cc81d172d4cffe43305213156c9e4
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/AuditTableWithSearch.jsx
@@ -0,0 +1,40 @@
+/* @flow */
+
+import React from "react";
+
+import AuditTable from "./AuditTable";
+import AuditParameters from "../components/AuditParameters";
+
+import { t } from "ttag";
+import { updateIn } from "icepick";
+
+import type { AuditDashCard } from "../types";
+
+type Props = {
+  placeholder?: string,
+  table: AuditDashCard,
+};
+
+// AuditTable but with a default search parameter that gets appended to `args`
+const AuditTableWithSearch = ({
+  placeholder = t`Search`,
+  table,
+  ...props
+}: Props) => (
+  <AuditParameters parameters={[{ key: "search", placeholder }]}>
+    {({ search }) => (
+      <AuditTable
+        {...props}
+        table={
+          search
+            ? updateIn(table, ["card", "dataset_query", "args"], args =>
+                (args || []).concat(search),
+              )
+            : table
+        }
+      />
+    )}
+  </AuditParameters>
+);
+
+export default AuditTableWithSearch;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/containers/QuestionLoadAndDisplay.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/QuestionLoadAndDisplay.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9a3bb4c964aaff0e839de229a6e629ca1c38d17e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/containers/QuestionLoadAndDisplay.jsx
@@ -0,0 +1,17 @@
+import React from "react";
+
+import QuestionResultLoader from "metabase/containers/QuestionResultLoader";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import Visualization from "metabase/visualizations/components/Visualization";
+
+const QuestionLoadAndDisplay = ({ question, onLoad, ...props }) => (
+  <QuestionResultLoader question={question} onLoad={onLoad}>
+    {({ loading, error, ...resultProps }) => (
+      <LoadingAndErrorWrapper loading={loading} error={error} noWrapper>
+        {() => <Visualization {...props} {...resultProps} />}
+      </LoadingAndErrorWrapper>
+    )}
+  </QuestionResultLoader>
+);
+
+export default QuestionLoadAndDisplay;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/index.js b/enterprise/frontend/src/metabase-enterprise/audit_app/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..884359248e15009bfd81481f57157a0f3964ae25
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/index.js
@@ -0,0 +1,11 @@
+import { PLUGIN_ADMIN_NAV_ITEMS, PLUGIN_ADMIN_ROUTES } from "metabase/plugins";
+
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import { t } from "ttag";
+
+import getAuditRoutes from "./routes";
+
+if (hasPremiumFeature("audit_app")) {
+  PLUGIN_ADMIN_NAV_ITEMS.push({ name: t`Audit`, path: "/admin/audit" });
+  PLUGIN_ADMIN_ROUTES.push(getAuditRoutes);
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboard_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboard_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f786d1834198bd2e063cc32c098719cc42484c0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboard_detail.js
@@ -0,0 +1,63 @@
+/* @flow */
+
+export const viewsByTime = (dashboardId: number) => ({
+  card: {
+    name: "Views per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboard-detail/views-by-time",
+      args: [dashboardId, "day"], // FIXME: should this be automatic?
+    },
+  },
+});
+
+export const revisionHistory = (dashboardId: number) => ({
+  card: {
+    name: "Revision history",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboard-detail/revision-history",
+      args: [dashboardId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "change_made", enabled: true },
+        { name: "revision_id", enabled: true },
+        { name: "timestamp", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
+
+export const cards = (dashboardId: number) => ({
+  card: {
+    name: "Cards",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboard-detail/cards",
+      args: [dashboardId],
+    },
+  },
+});
+
+export const auditLog = (dashboardId: number) => ({
+  card: {
+    name: "Audit log",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboard-detail/audit-log",
+      args: [dashboardId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "when", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboards.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboards.js
new file mode 100644
index 0000000000000000000000000000000000000000..0c47189e2957988ca5ac2587e1b35337a5f1c40d
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/dashboards.js
@@ -0,0 +1,86 @@
+/* @flow */
+
+//  DEPRECATED: use `views-and-saves-by-time ` instead.
+export const viewsPerDay = () => ({
+  card: {
+    name: "Total dashboard views per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboards/views-per-day",
+      args: [],
+    },
+  },
+});
+
+export const viewsAndSavesByTime = () => ({
+  card: {
+    name: "Dashboard views and saves per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboards/views-and-saves-by-time",
+      args: ["day"],
+    },
+    visualization_settings: {
+      "graph.y_axis.axis_enabled": true,
+    },
+  },
+});
+
+export const mostPopularAndSpeed = () => ({
+  card: {
+    name: "Most popular dashboards and their avg loading times",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn:
+        "metabase-enterprise.audit.pages.dashboards/most-popular-with-avg-speed",
+      args: [],
+    },
+  },
+});
+
+export const mostCommonQuestions = () => ({
+  card: {
+    name: "Questions included the most in dashboards",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboards/most-common-questions",
+      args: [],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Dashboards",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.dashboards/table",
+      args: [],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "dashboard_id", enabled: true },
+        { name: "total_views", enabled: true },
+        { name: "average_execution_time_ms", enabled: true },
+        { name: "cards", enabled: true },
+        { name: "saved_by_id", enabled: true },
+        {
+          name: "public_link",
+          enabled: true,
+          markdown_template: "[Link]({{value}})",
+        },
+        { name: "saved_on", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+        {
+          name: "last_edited_on",
+          enabled: true,
+          date_format: "M/D/YYYY, h:mm A",
+        },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/database_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/database_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fab45f849f258e826558b34fe93198d3873739e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/database_detail.js
@@ -0,0 +1,22 @@
+/* @flow */
+
+export const auditLog = (databaseId: number) => ({
+  card: {
+    name: "Audit log",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.database-detail/audit-log",
+      args: [databaseId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "card_id", enabled: true },
+        { name: "schema", enabled: true },
+        { name: "table_id", enabled: true },
+        { name: "started_at", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/databases.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/databases.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ec4781fc9c5cc194a5c3077cdb0af0beb2de409
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/databases.js
@@ -0,0 +1,77 @@
+/* @flow */
+
+export const totalQueryExecutionsByDb = () => ({
+  card: {
+    name: "Total queries and their average speed",
+    display: "bar",
+    dataset_query: {
+      type: "internal",
+      fn:
+        "metabase-enterprise.audit.pages.databases/total-query-executions-by-db",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.metrics": ["queries", "avg_running_time"],
+      "graph.dimensions": ["database_id"],
+      "graph.x_axis.title_text": "Database",
+      "graph.x_axis.axis_enabled": true,
+      "graph.y_axis.axis_enabled": true,
+      "graph.y_axis.auto_split": true,
+    },
+  },
+});
+
+// DEPRECATED
+export const queryExecutionsPerDbPerDay = () => ({
+  card: {
+    name: "Queries per database each day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn:
+        "metabase-enterprise.audit.pages.databases/query-executions-per-db-per-day",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["date", "database_id"],
+      "graph.metrics": ["count"],
+    },
+  },
+});
+
+export const queryExecutionsByTime = () => ({
+  card: {
+    name: "Query executions per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.databases/query-executions-by-time",
+      args: ["day"],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["date", "database_id"],
+      "graph.metrics": ["count"],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Databases",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.databases/table",
+      args: searchString ? [searchString] : [],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "database_id", enabled: true },
+        { name: "schemas", enabled: true },
+        { name: "tables", enabled: true },
+        { name: "sync_schedule", enabled: true },
+        { name: "added_on", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/downloads.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/downloads.js
new file mode 100644
index 0000000000000000000000000000000000000000..0475ffc76c8dbccb3ca025d5494c4fb1677e75e1
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/downloads.js
@@ -0,0 +1,56 @@
+export const perDayBySize = () => ({
+  card: {
+    name: "Largest downloads in the last 30 days",
+    display: "scatter",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.downloads/per-day-by-size",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["date"],
+      "graph.metrics": ["rows"],
+      "scatter.bubble": null,
+    },
+  },
+});
+
+export const perUser = () => ({
+  card: {
+    name: "Total downloads per user",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.downloads/per-user",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["user_id"],
+      "graph.metrics": ["downloads"],
+    },
+  },
+});
+
+export const bySize = () => ({
+  card: {
+    name: "All downloads by size",
+    display: "bar",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.downloads/by-size",
+      args: [],
+    },
+  },
+});
+
+export const table = () => ({
+  card: {
+    name: "Downloads",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.downloads/table",
+      args: [],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/queries.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/queries.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d22db1f229132147cf4cf119d3bba577f250353
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/queries.js
@@ -0,0 +1,82 @@
+/* @flow */
+
+export const viewsAndAvgExecutionTimeByDay = () => ({
+  card: {
+    name: "Query views and speed per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn:
+        "metabase-enterprise.audit.pages.queries/views-and-avg-execution-time-by-day",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.metrics": ["queries", "avg_running_time"],
+      "graph.dimensions": ["database"],
+      "graph.x_axis.title_text": "Time",
+      "graph.x_axis.axis_enabled": true,
+      "graph.y_axis.axis_enabled": true,
+      "graph.y_axis.auto_split": true,
+    },
+  },
+});
+
+export const mostPopular = () => ({
+  card: {
+    name: "Most popular queries",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.queries/most-popular",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.metrics": ["executions"],
+      "graph.dimensions": ["card_id"],
+    },
+  },
+});
+
+export const slowest = () => ({
+  card: {
+    name: "Slowest queries",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.queries/slowest",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.metrics": ["avg_running_time"],
+      "graph.dimensions": ["card_id"],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Questions",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.queries/table",
+      args: searchString ? [searchString] : [],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "card_id", enabled: true },
+        { name: "collection_id", enabled: true },
+        { name: "database_id", enabled: true },
+        { name: "table_id", enabled: true },
+        { name: "user_id", enabled: true },
+        {
+          name: "public_link",
+          enabled: true,
+          markdown_template: "[Link]({{value}})",
+        },
+        { name: "cache_ttl", enabled: true },
+        { name: "total_views", enabled: true },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/query_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/query_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..085973daaeda6da71fa5625cc2f3cab72f709d7e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/query_detail.js
@@ -0,0 +1,13 @@
+/* @flow*/
+
+export const details = (queryHash: string) => ({
+  card: {
+    name: "Query details",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.query-detail/details",
+      args: [queryHash],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/question_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/question_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..28e830cc967312b034c7a8e408d386f26a60458c
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/question_detail.js
@@ -0,0 +1,51 @@
+/* @flow */
+
+export const viewsByTime = (questionId: number) => ({
+  card: {
+    name: "Views per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.question-detail/views-by-time",
+      args: [questionId, "day"], // FIXME: should this be automatic?
+    },
+  },
+});
+
+export const revisionHistory = (questionId: number) => ({
+  card: {
+    name: "Revision history",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.question-detail/revision-history",
+      args: [questionId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "change_made", enabled: true },
+        { name: "revision_id", enabled: true },
+        { name: "timestamp", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
+
+export const auditLog = (questionId: number) => ({
+  card: {
+    name: "Audit log",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.question-detail/audit-log",
+      args: [questionId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "when", enabled: true },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/schemas.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/schemas.js
new file mode 100644
index 0000000000000000000000000000000000000000..966475f26d81674633a9cc89932bc7e04b79c78d
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/schemas.js
@@ -0,0 +1,37 @@
+/* @flow */
+
+export const mostQueried = () => ({
+  card: {
+    name: "Most-queried schemas",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.schemas/most-queried",
+      args: [],
+    },
+  },
+});
+
+export const slowestSchemas = () => ({
+  card: {
+    name: "Slowest schemas",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.schemas/slowest-schemas",
+      args: [],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Schemas",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.schemas/table",
+      args: searchString ? [searchString] : [],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/table_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/table_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b1e4a84ca65393b6ea5645e22e47fb8c75c72e3
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/table_detail.js
@@ -0,0 +1,20 @@
+/* @flow */
+
+export const auditLog = (tableId: number) => ({
+  card: {
+    name: "Audit log",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.table-detail/audit-log",
+      args: [tableId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "card_id", enabled: true },
+        { name: "started_at", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/tables.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/tables.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c9bb38d752a0a98a021723ebe25e6554756de8e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/tables.js
@@ -0,0 +1,45 @@
+/* @flow */
+
+export const mostQueried = () => ({
+  card: {
+    name: "Most-queried tables",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.tables/most-queried",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["table_id"],
+      "graph.metrics": ["executions"],
+    },
+  },
+});
+
+export const leastQueried = () => ({
+  card: {
+    name: "Least-queried tables",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.tables/least-queried",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["table_id"],
+      "graph.metrics": ["executions"],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Tables",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.tables/table",
+      args: searchString ? [searchString] : [],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/user_detail.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/user_detail.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddf3227c88cb989d79f6eed392ea3c6d90c5c14e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/user_detail.js
@@ -0,0 +1,145 @@
+/* @flow */
+
+export const table = (userId: number) => ({
+  card: {
+    name: "Most-viewed Dashboards",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/table",
+      args: [userId],
+    },
+  },
+});
+
+export const mostViewedDashboards = (userId: number) => ({
+  card: {
+    name: "Most-viewed Dashboards",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/most-viewed-dashboards",
+      args: [userId],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["dashboard_id"],
+      "graph.metrics": ["count"],
+    },
+  },
+});
+
+export const mostViewedQuestions = (userId: number) => ({
+  card: {
+    name: "Most-viewed Queries",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/most-viewed-questions",
+      args: [userId],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["card_id"],
+      "graph.metrics": ["count"],
+    },
+  },
+});
+
+export const objectViewsByTime = (userId: number) => ({
+  card: {
+    name: "Query views",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/object-views-by-time",
+      args: [userId, "card", "day"],
+    },
+  },
+  series: [
+    {
+      name: "Dashboard views",
+      display: "line",
+      dataset_query: {
+        type: "internal",
+        fn: "metabase-enterprise.audit.pages.user-detail/object-views-by-time",
+        args: [userId, "dashboard", "day"],
+      },
+    },
+  ],
+});
+
+export const queryViews = (userId: number) => ({
+  card: {
+    name: "Query views",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/query-views",
+      args: [userId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "card_id", enabled: true },
+        { name: "type", enabled: true },
+        { name: "database_id", enabled: true },
+        { name: "table_id", enabled: true },
+        { name: "collection_id", enabled: true },
+        { name: "viewed_on", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+});
+
+export const dashboardViews = (userId: number) => ({
+  card: {
+    name: "Dashboard views",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/dashboard-views",
+      args: [userId],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "dashboard_id", enabled: true },
+        { name: "collection_id", enabled: true },
+        { name: "timestamp", enabled: true },
+      ],
+    },
+  },
+});
+
+export const createdDashboards = (userId: number) => ({
+  card: {
+    name: "Created dashboards",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/created-dashboards",
+      args: [userId],
+    },
+  },
+});
+
+export const createdQuestions = (userId: number) => ({
+  card: {
+    name: "Created questions",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/created-questions",
+      args: [userId],
+    },
+  },
+});
+
+export const downloads = (userId: number) => ({
+  card: {
+    name: "Downloads",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.user-detail/downloads",
+      args: [userId],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/users.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..b9fd46fc0498712187c7ecb8af09973dbf8aaa5f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/cards/users.js
@@ -0,0 +1,157 @@
+/* @flow */
+
+export const activeAndNewByTime = () => ({
+  card: {
+    name: "Active members and new members per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/active-and-new-by-time",
+      args: ["day"],
+    },
+    visualization_settings: {
+      "graph.metrics": ["active_users", "new_users"],
+      "graph.dimensions": ["date"],
+      "graph.x_axis.title_text": "Time",
+      "graph.x_axis.axis_enabled": true,
+      "graph.y_axis.title_text": "Count",
+      "graph.y_axis.axis_enabled": true,
+      "graph.y_axis.auto_split": false,
+    },
+  },
+});
+
+export const activeUsersAndQueriesByDay = () => ({
+  card: {
+    name: "Active members and queries per day",
+    display: "line",
+    dataset_query: {
+      type: "internal",
+      fn:
+        "metabase-enterprise.audit.pages.users/active-users-and-queries-by-day",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.metrics": ["users", "queries"],
+      "graph.dimensions": ["day"],
+      "graph.x_axis.title_text": "Time",
+      "graph.x_axis.axis_enabled": true,
+      "graph.y_axis.title_text": "Count",
+      "graph.y_axis.axis_enabled": true,
+      "graph.y_axis.auto_split": false,
+    },
+  },
+});
+
+export const mostActive = () => ({
+  card: {
+    name: "Members who are looking at the most things",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/most-active",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.x_axis.axis_enabled": true,
+      "graph.x_axis.title_text": "Views",
+      "graph.dimensions": ["user_id"],
+      "graph.metrics": ["count"],
+    },
+  },
+});
+
+export const mostSaves = () => ({
+  card: {
+    name: "Members who are creating the most things",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/most-saves",
+      args: [],
+    },
+    visualization_settings: {
+      "graph.dimensions": ["user_id"],
+      "graph.metrics": ["saves"],
+    },
+  },
+});
+
+export const queryExecutionTimePerUser = () => ({
+  card: {
+    name: "Query execution time per member",
+    display: "row",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/query-execution-time-per-user",
+      args: [],
+    },
+  },
+});
+
+export const table = (searchString?: string) => ({
+  card: {
+    name: "Users",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/table",
+      args: searchString ? [searchString] : [],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "user_id", enabled: true },
+        { name: "groups", enabled: true },
+        { name: "date_joined", enabled: true, date_format: "M/D/YYYY" },
+        { name: "last_active", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+        { name: "signup_method", enabled: true },
+      ],
+    },
+  },
+});
+
+export const auditLog = () => ({
+  card: {
+    name: "Query views",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/query-views",
+      args: [],
+    },
+    visualization_settings: {
+      "table.columns": [
+        { name: "card_id", enabled: true },
+        { name: "viewed_by_id", enabled: true },
+        { name: "type", enabled: true },
+        { name: "database_id", enabled: true },
+        { name: "table_id", enabled: true },
+        { name: "collection_id", enabled: true },
+        { name: "viewed_on", enabled: true, date_format: "M/D/YYYY, h:mm A" },
+      ],
+    },
+  },
+  series: [
+    {
+      name: "Dashboard views",
+      display: "table",
+      dataset_query: {
+        type: "internal",
+        fn: "metabase-enterprise.audit.pages.users/dashboard-views",
+        args: [],
+      },
+    },
+  ],
+});
+
+export const dashboardViews = () => ({
+  card: {
+    name: "Dashboard views",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.users/dashboard-views",
+      args: [],
+    },
+  },
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/lib/util.js b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f2b5e14d2f91a7a47ef1a24e0d24502c790a150
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/lib/util.js
@@ -0,0 +1,80 @@
+/* @flow */
+
+import _ from "underscore";
+
+import Question from "metabase-lib/lib/Question";
+
+import type {
+  ClickObject,
+  QueryMode,
+} from "metabase-types/types/Visualization";
+
+const columnNameToUrl = {
+  user_id: value => `/admin/audit/member/${value}`,
+  viewed_by_id: value => `/admin/audit/member/${value}`,
+  saved_by_id: value => `/admin/audit/member/${value}`,
+  dashboard_id: value => `/admin/audit/dashboard/${value}`,
+  card_id: value => `/admin/audit/question/${value}`,
+  database_id: value => `/admin/audit/database/${value}`,
+  // NOTE: disable schema links until schema detail is implemented
+  // schema: value => `/admin/audit/schema/${value}`,
+  table_id: value => `/admin/audit/table/${value}`,
+  // NOTE: query_hash uses standard Base64 encoding which isn't URL safe so make sure to escape it
+  query_hash: value =>
+    `/admin/audit/query/${encodeURIComponent(String(value))}`,
+};
+
+const AuditDrill = ({
+  question,
+  clicked,
+}: {
+  question: Question,
+  clicked?: ClickObject,
+}) => {
+  if (!clicked) {
+    return [];
+  }
+  const metricAndDimensions = [clicked].concat(clicked.dimensions || []);
+  for (const { column, value } of metricAndDimensions) {
+    if (column && columnNameToUrl[column.name] != null && value != null) {
+      return [
+        {
+          name: "detail",
+          title: `View this`,
+          default: true,
+          url() {
+            return columnNameToUrl[column.name](value);
+          },
+        },
+      ];
+    }
+  }
+
+  // NOTE: special case for showing query detail links for ad-hoc queries in the card id column
+  const { column, origin } = clicked;
+  if (origin && column && column.name === "card_id") {
+    const queryHashColIndex = _.findIndex(
+      origin.cols,
+      col => col.name === "query_hash",
+    );
+    const value = origin.row[queryHashColIndex];
+    if (value) {
+      return [
+        {
+          name: "detail",
+          title: `View this`,
+          default: true,
+          url() {
+            return `/admin/audit/query/${encodeURIComponent(String(value))}`;
+          },
+        },
+      ];
+    }
+  }
+  return [];
+};
+
+export const AuditMode: QueryMode = {
+  name: "audit",
+  drills: () => [AuditDrill],
+};
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboardDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboardDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d39120785f9d65ca5429ba84e53ffa0087f77fa5
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboardDetail.jsx
@@ -0,0 +1,61 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTable from "../containers/AuditTable";
+
+import OpenInMetabase from "../components/OpenInMetabase";
+
+import EntityName from "metabase/entities/containers/EntityName";
+
+import * as Urls from "metabase/lib/urls";
+
+import * as DashboardCards from "../lib/cards/dashboard_detail";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditDashboardDetail = ({ params, ...props }: Props) => {
+  const dashboardId = parseInt(params.dashboardId);
+  return (
+    <AuditContent
+      {...props}
+      title={<EntityName entityType="dashboards" entityId={dashboardId} />}
+      subtitle={<OpenInMetabase to={Urls.dashboard(dashboardId)} />}
+      tabs={AuditDashboardDetail.tabs}
+      dashboardId={dashboardId}
+    />
+  );
+};
+
+const AuditDashboardActivityTab = ({ dashboardId }) => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 18, h: 10 }, DashboardCards.viewsByTime(dashboardId)],
+    ]}
+  />
+);
+
+const AuditDashboardRevisionsTab = ({ dashboardId }) => (
+  <AuditTable table={DashboardCards.revisionHistory(dashboardId)} />
+);
+
+const AuditDashboardAuditLogTab = ({ dashboardId }) => (
+  <AuditTable table={DashboardCards.auditLog(dashboardId)} />
+);
+
+AuditDashboardDetail.tabs = [
+  { path: "activity", title: "Activity", component: AuditDashboardActivityTab },
+  { path: "details", title: "Details" },
+  {
+    path: "revisions",
+    title: "Revision history",
+    component: AuditDashboardRevisionsTab,
+  },
+  { path: "log", title: "Audit log", component: AuditDashboardAuditLogTab },
+];
+
+export default AuditDashboardDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboards.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboards.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..092cca1eae47632adf793c5d1c80844c7236b7a9
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDashboards.jsx
@@ -0,0 +1,49 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as DashboardCards from "../lib/cards/dashboards";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditDashboards = (props: Props) => (
+  <AuditContent {...props} title="Dashboards" tabs={AuditDashboards.tabs} />
+);
+
+const AuditDashboardsOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 18, h: 7 }, DashboardCards.viewsAndSavesByTime()],
+      [{ x: 0, y: 7, w: 11, h: 9 }, DashboardCards.mostPopularAndSpeed()],
+      [{ x: 12, y: 7, w: 6, h: 9 }, DashboardCards.mostCommonQuestions()],
+    ]}
+  />
+);
+
+const AuditDashboardsAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Dashboard name`}
+    table={DashboardCards.table()}
+  />
+);
+
+AuditDashboards.tabs = [
+  {
+    path: "overview",
+    title: "Overview",
+    component: AuditDashboardsOverviewTab,
+  },
+  {
+    path: "all",
+    title: "All dashboards",
+    component: AuditDashboardsAllTab,
+  },
+];
+
+export default AuditDashboards;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabaseDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabaseDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ac13128455ef4f71991393f38a98f92bb17bd765
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabaseDetail.jsx
@@ -0,0 +1,42 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditTable from "../containers/AuditTable";
+
+import EntityName from "metabase/entities/containers/EntityName";
+
+import * as DatabaseDetailCards from "../lib/cards/database_detail";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditDatabaseDetail = ({ params, ...props }: Props) => {
+  const databaseId = parseInt(params.databaseId);
+  return (
+    <AuditContent
+      {...props}
+      title={
+        <EntityName
+          entityType="databases"
+          entityId={databaseId}
+          property={"name"}
+        />
+      }
+      tabs={AuditDatabaseDetail.tabs}
+      databaseId={databaseId}
+    />
+  );
+};
+
+const AuditDatabaseAuditLogTab = ({ databaseId }) => (
+  <AuditTable table={DatabaseDetailCards.auditLog(databaseId)} />
+);
+
+AuditDatabaseDetail.tabs = [
+  { path: "log", title: "Audit log", component: AuditDatabaseAuditLogTab },
+];
+
+export default AuditDatabaseDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabases.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabases.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..48bdb7f90dcb24b897f0d7911a0a1aac93d18abb
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDatabases.jsx
@@ -0,0 +1,43 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as DatabasesCards from "../lib/cards/databases";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditDatabases = (props: Props) => (
+  <AuditContent {...props} title="Databases" tabs={AuditDatabases.tabs} />
+);
+
+const AuditDatabasesOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 18, h: 6 }, DatabasesCards.totalQueryExecutionsByDb()],
+      [
+        { x: 0, y: 6, w: 18, h: 6 },
+        DatabasesCards.queryExecutionsPerDbPerDay(),
+      ],
+    ]}
+  />
+);
+
+const AuditDatabasesAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Database name`}
+    table={DatabasesCards.table()}
+  />
+);
+
+AuditDatabases.tabs = [
+  { path: "overview", title: "Overview", component: AuditDatabasesOverviewTab },
+  { path: "all", title: "All databases", component: AuditDatabasesAllTab },
+];
+
+export default AuditDatabases;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDownloads.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDownloads.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dc1ebf5540b50063c23f1828cdd09c043874d628
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditDownloads.jsx
@@ -0,0 +1,38 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTable from "../containers/AuditTable";
+
+import * as DownloadsCards from "../lib/cards/downloads";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditDownloads = (props: Props) => (
+  <AuditContent {...props} title="Downloads" tabs={AuditDownloads.tabs} />
+);
+
+const AuditDownloadsOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 18, h: 9 }, DownloadsCards.perDayBySize()],
+      [{ x: 0, y: 9, w: 6, h: 9 }, DownloadsCards.perUser()],
+      [{ x: 6, y: 9, w: 12, h: 9 }, DownloadsCards.bySize()],
+    ]}
+  />
+);
+
+const AuditDownloadsAllTab = () => (
+  <AuditTable table={DownloadsCards.table()} />
+);
+
+AuditDownloads.tabs = [
+  { path: "overview", title: "Overview", component: AuditDownloadsOverviewTab },
+  { path: "all", title: "All downloads", component: AuditDownloadsAllTab },
+];
+
+export default AuditDownloads;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditOverview.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditOverview.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1205b473ebb00c1ee3ca21fee7d68cde126a20a7
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditOverview.jsx
@@ -0,0 +1,25 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+
+import * as UsersCards from "../lib/cards/users";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditOverview = (props: Props) => (
+  <AuditContent {...props} title="Overview">
+    <AuditDashboard
+      cards={[
+        [{ x: 0, y: 0, w: 18, h: 9 }, UsersCards.activeUsersAndQueriesByDay()],
+        [{ x: 0, y: 9, w: 18, h: 9 }, UsersCards.mostActive()],
+      ]}
+    />
+  </AuditContent>
+);
+
+export default AuditOverview;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQueryDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQueryDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..4aa89a29ccb7225a4161ea3d34fc79c8761da671
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQueryDetail.jsx
@@ -0,0 +1,112 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditCustomView from "../containers/AuditCustomView";
+
+import OpenInMetabase from "../components/OpenInMetabase";
+
+import NativeQueryEditor from "metabase/query_builder/components/NativeQueryEditor";
+import GuiQueryEditor from "metabase/query_builder/components/GuiQueryEditor";
+import Question from "metabase-lib/lib/Question";
+
+import * as QueryDetailCards from "../lib/cards/query_detail";
+
+import { serializeCardForUrl } from "metabase/lib/card";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditQueryDetail = ({ params: { queryHash } }: Props) => (
+  <AuditCustomView card={QueryDetailCards.details(queryHash)}>
+    {({ result }) => {
+      if (!result) {
+        return null;
+      }
+      const datasetQuery = result.data.rows[0][0];
+      if (!datasetQuery) {
+        return <div>Query Not Recorded, sorry</div>;
+      }
+
+      return (
+        <AuditContent
+          title="Query"
+          subtitle={
+            <OpenInMetabase
+              to={
+                "/question#" +
+                serializeCardForUrl({
+                  dataset_query: datasetQuery,
+                })
+              }
+            />
+          }
+        >
+          <div className="pt4" style={{ pointerEvents: "none" }}>
+            <QueryBuilderReadOnly
+              card={{
+                name: "",
+                visualization_settings: {},
+                display: "table",
+                dataset_query: datasetQuery,
+              }}
+            />
+          </div>
+        </AuditContent>
+      );
+    }}
+  </AuditCustomView>
+);
+
+import { connect } from "react-redux";
+import { getMetadata } from "metabase/selectors/metadata";
+
+import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
+
+import { loadMetadataForCard } from "metabase/query_builder/actions";
+
+const mapStateToProps = state => ({ metadata: getMetadata(state) });
+const mapDispatchToProps = { loadMetadataForCard };
+
+@connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)
+class QueryBuilderReadOnly extends React.Component {
+  componentDidMount() {
+    const { card, loadMetadataForCard } = this.props;
+    loadMetadataForCard(card);
+  }
+  render() {
+    const { card, metadata } = this.props;
+    const question = new Question(card, metadata);
+
+    const query = question.query();
+
+    if (query instanceof NativeQuery) {
+      return (
+        <NativeQueryEditor
+          question={question}
+          query={query}
+          location={{ query: {} }}
+          readOnly
+        />
+      );
+    } else {
+      const tableMetadata = query.table();
+      return tableMetadata ? (
+        <GuiQueryEditor
+          datasetQuery={card.dataset_query}
+          query={query}
+          databases={tableMetadata && [tableMetadata.db]}
+          setDatasetQuery={() => {}} // no-op to appease flow
+          readOnly
+        />
+      ) : null;
+    }
+  }
+}
+
+export default AuditQueryDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestionDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestionDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..74ee3c131e1cfef6d00808d9a6e6480e0bfc8abe
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestionDetail.jsx
@@ -0,0 +1,64 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTable from "../containers/AuditTable";
+
+import OpenInMetabase from "../components/OpenInMetabase";
+
+import EntityName from "metabase/entities/containers/EntityName";
+
+import * as Urls from "metabase/lib/urls";
+
+import * as QuestionDetailCards from "../lib/cards/question_detail";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditQuestionDetail = ({ params, ...props }: Props) => {
+  const questionId = parseInt(params.questionId);
+  return (
+    <AuditContent
+      {...props}
+      title={<EntityName entityType="questions" entityId={questionId} />}
+      subtitle={<OpenInMetabase to={Urls.question(questionId)} />}
+      tabs={AuditQuestionDetail.tabs}
+      questionId={questionId}
+    />
+  );
+};
+
+const AuditQuestionActivityTab = ({ questionId }) => (
+  <AuditDashboard
+    cards={[
+      [
+        { x: 0, y: 0, w: 18, h: 10 },
+        QuestionDetailCards.viewsByTime(questionId),
+      ],
+    ]}
+  />
+);
+
+const AuditQuestionRevisionsTab = ({ questionId }) => (
+  <AuditTable table={QuestionDetailCards.revisionHistory(questionId)} />
+);
+
+const AuditQuestionAuditLogTab = ({ questionId }) => (
+  <AuditTable table={QuestionDetailCards.auditLog(questionId)} />
+);
+
+AuditQuestionDetail.tabs = [
+  { path: "activity", title: "Activity", component: AuditQuestionActivityTab },
+  { path: "details", title: "Details" },
+  {
+    path: "revisions",
+    title: "Revision history",
+    component: AuditQuestionRevisionsTab,
+  },
+  { path: "log", title: "Audit log", component: AuditQuestionAuditLogTab },
+];
+
+export default AuditQuestionDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestions.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..817fd02d3c2dfb6c2ed117cad838e5896331151e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditQuestions.jsx
@@ -0,0 +1,44 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as QueriesCards from "../lib/cards/queries";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditQuestions = (props: Props) => (
+  <AuditContent {...props} title="Questions" tabs={AuditQuestions.tabs} />
+);
+
+const AuditQuestionsOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 9, h: 9 }, QueriesCards.mostPopular()],
+      [{ x: 9, y: 0, w: 9, h: 9 }, QueriesCards.slowest()],
+      [
+        { x: 0, y: 9, w: 18, h: 6 },
+        QueriesCards.viewsAndAvgExecutionTimeByDay(),
+      ],
+    ]}
+  />
+);
+
+const AuditQuestionsAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Question name`}
+    table={QueriesCards.table()}
+  />
+);
+
+AuditQuestions.tabs = [
+  { path: "overview", title: "Overview", component: AuditQuestionsOverviewTab },
+  { path: "all", title: "All questions", component: AuditQuestionsAllTab },
+];
+
+export default AuditQuestions;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemaDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemaDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a567ac8c91eac5a04fef41d79204db78ac18edc7
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemaDetail.jsx
@@ -0,0 +1,7 @@
+/* @flow */
+
+import React from "react";
+
+const AuditSchemaDetail = () => <div>todo schema</div>;
+
+export default AuditSchemaDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemas.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemas.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..720d359c2bb6ccd3f6520c6d647b7a0d49881756
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditSchemas.jsx
@@ -0,0 +1,40 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as SchemasCards from "../lib/cards/schemas";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditSchemas = (props: Props) => (
+  <AuditContent {...props} title="Schemas" tabs={AuditSchemas.tabs} />
+);
+
+const AuditSchemasOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 9, h: 9 }, SchemasCards.mostQueried()],
+      [{ x: 9, y: 0, w: 9, h: 9 }, SchemasCards.slowestSchemas()],
+    ]}
+  />
+);
+
+const AuditSchemasAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Schema name`}
+    table={SchemasCards.table()}
+  />
+);
+
+AuditSchemas.tabs = [
+  { path: "overview", title: "Overview", component: AuditSchemasOverviewTab },
+  { path: "all", title: "All schemas", component: AuditSchemasAllTab },
+];
+
+export default AuditSchemas;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTableDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTableDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..338f04f852a036592f5fa2d72a207e8bf268084e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTableDetail.jsx
@@ -0,0 +1,42 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditTable from "../containers/AuditTable";
+
+import EntityName from "metabase/entities/containers/EntityName";
+
+import * as TableDetailCards from "../lib/cards/table_detail";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditTableDetail = ({ params, ...props }: Props) => {
+  const tableId = parseInt(params.tableId);
+  return (
+    <AuditContent
+      {...props}
+      title={
+        <EntityName
+          entityType="tables"
+          entityId={tableId}
+          property={"display_name"}
+        />
+      }
+      tabs={AuditTableDetail.tabs}
+      tableId={tableId}
+    />
+  );
+};
+
+const AuditTableAuditLogTab = ({ tableId }) => (
+  <AuditTable table={TableDetailCards.auditLog(tableId)} />
+);
+
+AuditTableDetail.tabs = [
+  { path: "log", title: "Audit log", component: AuditTableAuditLogTab },
+];
+
+export default AuditTableDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTables.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTables.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9159f63365991db42110eff81906127a8897974f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditTables.jsx
@@ -0,0 +1,40 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as TablesCards from "../lib/cards/tables";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditTables = (props: Props) => (
+  <AuditContent {...props} title="Tables" tabs={AuditTables.tabs} />
+);
+
+const AuditTablesOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 9, h: 9 }, TablesCards.mostQueried()],
+      [{ x: 9, y: 0, w: 9, h: 9 }, TablesCards.leastQueried()],
+    ]}
+  />
+);
+
+const AuditTablesAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Table name`}
+    table={TablesCards.table()}
+  />
+);
+
+AuditTables.tabs = [
+  { path: "overview", title: "Overview", component: AuditTablesOverviewTab },
+  { path: "all", title: "All tables", component: AuditTablesAllTab },
+];
+
+export default AuditTables;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUserDetail.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUserDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f93dbf0db6bde0c45b2a012ed89460b8dfeae3d4
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUserDetail.jsx
@@ -0,0 +1,87 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTable from "../containers/AuditTable";
+
+import EntityName from "metabase/entities/containers/EntityName";
+
+import * as UserDetailCards from "../lib/cards/user_detail";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditUserDetail = ({ params, ...props }: Props) => {
+  const userId = parseInt(params.userId);
+  return (
+    <AuditContent
+      {...props}
+      title={
+        <EntityName
+          entityType="users"
+          entityId={userId}
+          property={"common_name"}
+        />
+      }
+      tabs={AuditUserDetail.tabs}
+      userId={userId}
+    />
+  );
+};
+
+const AuditUserActivityTab = ({ userId }) => (
+  <AuditDashboard
+    cards={[
+      // [{ x: 0, y: 0, w: 4, h: 4 }, UserDetailCards.questions(userId)],
+      // [{ x: 4, y: 0, w: 4, h: 4 }, UserDetailCards.dashboards(userId)],
+      // [{ x: 8, y: 0, w: 4, h: 4 }, UserDetailCards.pulses(userId)],
+      // [{ x: 12, y: 0, w: 4, h: 4 }, UserDetailCards.collections(userId)],
+      [
+        { x: 0, y: 0, w: 8, h: 8 },
+        UserDetailCards.mostViewedDashboards(userId),
+      ],
+      [{ x: 8, y: 0, w: 8, h: 8 }, UserDetailCards.mostViewedQuestions(userId)],
+      [{ x: 0, y: 8, w: 16, h: 8 }, UserDetailCards.objectViewsByTime(userId)],
+    ]}
+  />
+);
+
+const AuditUserQueryViewsTab = ({ userId }) => (
+  <AuditTable table={UserDetailCards.queryViews(userId)} />
+);
+
+const AuditUserDashboardViewsTab = ({ userId }) => (
+  <AuditTable table={UserDetailCards.dashboardViews(userId)} />
+);
+
+const AuditUserDownloadsTab = ({ userId }) => (
+  <AuditTable table={UserDetailCards.downloads(userId)} />
+);
+
+AuditUserDetail.tabs = [
+  { path: "activity", title: "Activity", component: AuditUserActivityTab },
+  { path: "details", title: "Account details" },
+  { path: "data_permissions", title: "Data permissions" },
+  { path: "collection_permissions", title: "Collection permissions" },
+  { path: "made_by", title: "Made by them" },
+  {
+    path: "query_views",
+    title: "Query views",
+    component: AuditUserQueryViewsTab,
+  },
+  {
+    path: "dashboard_views",
+    title: "Dashboard views",
+    component: AuditUserDashboardViewsTab,
+  },
+  {
+    path: "downloads",
+    title: "Downloads",
+    component: AuditUserDownloadsTab,
+  },
+];
+
+export default AuditUserDetail;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUsers.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUsers.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..da00b3ae023f70c1236985459ae83e152596493c
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/pages/AuditUsers.jsx
@@ -0,0 +1,47 @@
+/* @flow */
+
+import React from "react";
+
+import AuditContent from "../components/AuditContent";
+import AuditDashboard from "../containers/AuditDashboard";
+import AuditTable from "../containers/AuditTable";
+import AuditTableWithSearch from "../containers/AuditTableWithSearch";
+
+import * as UsersCards from "../lib/cards/users";
+
+type Props = {
+  params: { [key: string]: string },
+};
+
+const AuditUsers = (props: Props) => (
+  <AuditContent {...props} title="Team members" tabs={AuditUsers.tabs} />
+);
+
+const AuditUsersOverviewTab = () => (
+  <AuditDashboard
+    cards={[
+      [{ x: 0, y: 0, w: 18, h: 9 }, UsersCards.activeAndNewByTime()],
+      [{ x: 0, y: 9, w: 9, h: 9 }, UsersCards.mostActive()],
+      [{ x: 9, y: 9, w: 9, h: 9 }, UsersCards.mostSaves()],
+    ]}
+  />
+);
+
+const AuditUsersAllTab = () => (
+  <AuditTableWithSearch
+    placeholder={`Member name`}
+    table={UsersCards.table()}
+  />
+);
+
+const AuditUsersAuditLogTab = () => (
+  <AuditTable table={UsersCards.auditLog()} />
+);
+
+AuditUsers.tabs = [
+  { path: "overview", title: "Overview", component: AuditUsersOverviewTab },
+  { path: "all", title: "All members", component: AuditUsersAllTab },
+  { path: "log", title: "Audit log", component: AuditUsersAuditLogTab },
+];
+
+export default AuditUsers;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/routes.jsx b/enterprise/frontend/src/metabase-enterprise/audit_app/routes.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..aa68463a09af38e562aa1391999c9020637e4d9c
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/routes.jsx
@@ -0,0 +1,100 @@
+/* @flow */
+
+import React from "react";
+
+import { Route } from "metabase/hoc/Title";
+import { IndexRoute, IndexRedirect } from "react-router";
+import { t } from "ttag";
+import _ from "underscore";
+
+import AuditApp from "./containers/AuditApp";
+
+import AuditOverview from "./pages/AuditOverview";
+
+import AuditDatabases from "./pages/AuditDatabases";
+import AuditDatabaseDetail from "./pages/AuditDatabaseDetail";
+import AuditSchemas from "./pages/AuditSchemas";
+import AuditSchemaDetail from "./pages/AuditSchemaDetail";
+import AuditTables from "./pages/AuditTables";
+import AuditTableDetail from "./pages/AuditTableDetail";
+
+import AuditQuestions from "./pages/AuditQuestions";
+import AuditQuestionDetail from "./pages/AuditQuestionDetail";
+import AuditDashboards from "./pages/AuditDashboards";
+import AuditDashboardDetail from "./pages/AuditDashboardDetail";
+import AuditQueryDetail from "./pages/AuditQueryDetail";
+
+import AuditUsers from "./pages/AuditUsers";
+import AuditUserDetail from "./pages/AuditUserDetail";
+
+import AuditDownloads from "./pages/AuditDownloads";
+
+type Page = {
+  tabs?: Tab[],
+};
+
+type Tab = {
+  path: string,
+  title: string,
+  component?: any,
+};
+
+function getPageRoutes(path, page: Page) {
+  const subRoutes = [];
+  // add a redirect for the default tab
+  const defaultTab = getDefaultTab(page);
+  if (defaultTab) {
+    subRoutes.push(<IndexRedirect to={defaultTab.path} />);
+  }
+  // add sub routes for each tab
+  if (page.tabs) {
+    subRoutes.push(
+      ...page.tabs.map(tab => (
+        <Route path={tab.path} component={tab.component} />
+      )),
+    );
+  }
+  // if path is provided, use that, otherwise use an IndexRoute
+  return path ? (
+    <Route path={path} component={page}>
+      {subRoutes}
+    </Route>
+  ) : (
+    <IndexRoute component={page}>{subRoutes}</IndexRoute>
+  );
+}
+
+function getDefaultTab(page: Page): ?Tab {
+  // use the tab with "default = true" or the first
+  return (
+    _.findWhere(page.tabs, { default: true }) ||
+    (page.tabs && page.tabs[0]) ||
+    null
+  );
+}
+
+const getRoutes = (store: any) => (
+  <Route path="audit" title={t`Audit`} component={AuditApp}>
+    {/* <IndexRedirect to="overview" /> */}
+    <IndexRedirect to="members" />
+
+    <Route path="overview" component={AuditOverview} />
+
+    {getPageRoutes("databases", AuditDatabases)}
+    {getPageRoutes("database/:databaseId", AuditDatabaseDetail)}
+    {getPageRoutes("schemas", AuditSchemas)}
+    {getPageRoutes("schema/:schemaId", AuditSchemaDetail)}
+    {getPageRoutes("tables", AuditTables)}
+    {getPageRoutes("table/:tableId", AuditTableDetail)}
+    {getPageRoutes("dashboards", AuditDashboards)}
+    {getPageRoutes("dashboard/:dashboardId", AuditDashboardDetail)}
+    {getPageRoutes("questions", AuditQuestions)}
+    {getPageRoutes("question/:questionId", AuditQuestionDetail)}
+    {getPageRoutes("query/:queryHash", AuditQueryDetail)}
+    {getPageRoutes("downloads", AuditDownloads)}
+    {getPageRoutes("members", AuditUsers)}
+    {getPageRoutes("member/:userId", AuditUserDetail)}
+  </Route>
+);
+
+export default getRoutes;
diff --git a/enterprise/frontend/src/metabase-enterprise/audit_app/types.js b/enterprise/frontend/src/metabase-enterprise/audit_app/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..5fe422df9e741c7a5b83de3e3b1d30495cbc48c0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/audit_app/types.js
@@ -0,0 +1,17 @@
+/* @flow */
+
+import type { Card } from "metabase-types/types/Card";
+
+export type AuditDashCard = {
+  card: Card,
+  series?: Card[],
+};
+
+export type AuditCardPosition = {
+  x: number,
+  y: number,
+  w: number,
+  h: number,
+};
+
+export type AuditCard = [AuditCardPosition, AuditDashCard];
diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e34371f5e027f5ad0c69be29814bd829da63301f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SSOButton.jsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+import { IFRAMED } from "metabase/lib/dom";
+import MetabaseAnalytics from "metabase/lib/analytics";
+import MetabaseSettings from "metabase/lib/settings";
+
+import AuthProviderButton from "metabase/auth/components/AuthProviderButton";
+
+export default class SSOButton extends React.Component {
+  componentWillMount() {
+    // If we're iframed and immediately simulate a click
+    if (IFRAMED) {
+      this.handleClick();
+    }
+  }
+
+  handleClick = () => {
+    const { redirect } = this.props.location.query;
+    MetabaseAnalytics.trackEvent("Auth", "SSO Login Start");
+    // use `window.location` instead of `push` since it's not a frontend route
+    window.location =
+      MetabaseSettings.get("site-url") +
+      "/auth/sso" +
+      (redirect ? "?redirect=" + encodeURIComponent(redirect) : "");
+  };
+
+  render() {
+    return <AuthProviderButton {...this.props} onClick={this.handleClick} />;
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsJWTForm.jsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsJWTForm.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a6a507b0f0b9b5df2d70ee7a2bc2f1d667bcba75
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsJWTForm.jsx
@@ -0,0 +1,42 @@
+import React, { Component } from "react";
+import { t } from "ttag";
+
+import SettingsBatchForm from "metabase/admin/settings/components/SettingsBatchForm";
+
+export default class SettingsJWTForm extends Component {
+  render() {
+    return (
+      <SettingsBatchForm
+        {...this.props}
+        breadcrumbs={[
+          [t`Authentication`, "/admin/settings/authentication"],
+          [t`JWT`],
+        ]}
+        enabledKey="jwt-enabled"
+        layout={[
+          {
+            title: t`Server Settings`,
+            settings: [
+              "jwt-enabled",
+              "jwt-identity-provider-uri",
+              "jwt-shared-secret",
+            ],
+          },
+          {
+            title: t`User attribute configuration (optional)`,
+            collapse: true,
+            settings: [
+              "jwt-attribute-email",
+              "jwt-attribute-firstname",
+              "jwt-attribute-lastname",
+            ],
+          },
+          {
+            title: t`Group Schema`,
+            settings: ["jwt-group-sync"],
+          },
+        ]}
+      />
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0967e4726b214cce94f0c6864e60a1b0f196bf35
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/auth/components/SettingsSAMLForm.jsx
@@ -0,0 +1,197 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import { t } from "ttag";
+import _ from "underscore";
+import { Box } from "grid-styled";
+
+import { updateSettings } from "metabase/admin/settings/settings";
+
+import Form, {
+  FormField,
+  FormSubmit,
+  FormMessage,
+  FormSection,
+} from "metabase/containers/Form";
+
+import Breadcrumbs from "metabase/components/Breadcrumbs";
+import CopyWidget from "metabase/components/CopyWidget";
+
+import GroupMappingsWidget from "metabase/admin/settings/components/widgets/GroupMappingsWidget";
+
+import MetabaseSettings from "metabase/lib/settings";
+
+const settingToFormField = setting => ({
+  name: setting.key,
+  description: setting.description,
+  placeholder: setting.is_env_setting
+    ? t`Using ${setting.env_name}`
+    : setting.placeholder || setting.default,
+  validate: setting.required ? value => !value && "required" : null,
+});
+
+@connect(
+  null,
+  { updateSettings },
+)
+export default class SettingsSAMLForm extends Component {
+  render() {
+    const { elements, settingValues, updateSettings } = this.props;
+    // TODO: move these to an outer component so we don't have to do it in every form page
+    const setting = name =>
+      _.findWhere(elements, { key: name }) || { key: name };
+    const settingField = name => settingToFormField(setting(name));
+
+    const initialValues = { ...settingValues };
+
+    // HACK: this is to make the default show up as selectable text instead of placeholder
+    const addDefaultAsInitialValue = name => {
+      if (initialValues[name] == null && setting(name).default != null) {
+        initialValues[name] = setting(name).default;
+      }
+    };
+    addDefaultAsInitialValue("saml-attribute-email");
+    addDefaultAsInitialValue("saml-attribute-firstname");
+    addDefaultAsInitialValue("saml-attribute-lastname");
+
+    const acsConsumerUrl = MetabaseSettings.get("site-url") + "/auth/sso";
+
+    return (
+      <Form
+        className="mx2"
+        style={{ maxWidth: 520 }}
+        initialValues={initialValues}
+        onSubmit={updateSettings}
+      >
+        <Breadcrumbs
+          className="mb3"
+          crumbs={[
+            [t`Authentication`, "/admin/settings/authentication"],
+            [t`SAML`],
+          ]}
+        />
+        <h2 className="mb3">{t`Set up SAML-based SSO`}</h2>
+        <FormField
+          {...settingField("saml-enabled")}
+          name="saml-enabled"
+          title={t`SAML Authentication`}
+          type="boolean"
+          showEnabledLabel={false}
+        />
+        <Box className="bordered rounded" px={3} pt={3} pb={2} mb={2}>
+          <h3 className="mb0">{t`Configure your identity provider (IdP)`}</h3>
+          <p className="mb4 mt1 text-medium">{t`Your identity provider will need the following info about Metabase.`}</p>
+
+          <div className="Form-field" s>
+            <div className="Form-label">{t`URL the IdP should redirect back to`}</div>
+            <div className="pb1">{t`This is called the Single Sign On URL in Okta, the Application Callback URL in Auth0,
+                                  and the ACS (Consumer) URL in OneLogin. `}</div>
+            <CopyWidget value={acsConsumerUrl} />
+          </div>
+
+          <h4 className="pt2">{t`SAML attributes`}</h4>
+          <p className="mb3 mt1 text-medium">{t`In most IdPs, you'll need to put each of these in an input box labeled
+                        "Name" in the attribute statements section.`}</p>
+
+          <FormField
+            {...settingField("saml-attribute-email")}
+            title={t`User's email attribute`}
+            type={({ field }) => <CopyWidget {...field} />}
+          />
+          <FormField
+            {...settingField("saml-attribute-firstname")}
+            title={t`User's first name attribute`}
+            type={({ field }) => <CopyWidget {...field} />}
+          />
+          <FormField
+            {...settingField("saml-attribute-lastname")}
+            title={t`User's last name attribute`}
+            type={({ field }) => <CopyWidget {...field} />}
+          />
+        </Box>
+
+        <Box className="bordered rounded" px={3} pt={3} pb={2} mb={2}>
+          <h3 className="mb0">{t`Tell Metabase about your identity provider`}</h3>
+          <p className="mb4 mt1 text-medium">{t`Metabase will need the following info about your provider.`}</p>
+          <FormField
+            {...settingField("saml-identity-provider-uri")}
+            title={t`SAML Identity Provider URL`}
+            placeholder="https://your-org-name.yourIDP.com"
+            required
+            autoFocus
+          />
+          <FormField
+            {...settingField("saml-identity-provider-certificate")}
+            title={t`SAML Identity Provider Certificate`}
+            type="text"
+            required
+            monospaceText
+          />
+          <FormField
+            {...settingField("saml-application-name")}
+            title={t`SAML Application Name`}
+          />
+          <FormField
+            {...settingField("saml-identity-provider-issuer")}
+            title={t`SAML Identity Provider Issuer`}
+          />
+        </Box>
+
+        <Box className="bordered rounded" px={3} pt={3} pb={1} mb={2}>
+          <FormSection title={t`Sign SSO requests (optional)`} collapsible>
+            <FormField
+              {...settingField("saml-keystore-path")}
+              title={t`SAML Keystore Path`}
+            />
+            <FormField
+              {...settingField("saml-keystore-password")}
+              title={t`SAML Keystore Password`}
+              type="password"
+              placeholder={t`Shh...`}
+            />
+            <FormField
+              {...settingField("saml-keystore-alias")}
+              title={t`SAML Keystore Alias`}
+            />
+          </FormSection>
+        </Box>
+
+        <Box className="bordered rounded" px={3} pt={3} pb={2} mb={2}>
+          <h3 className="mb0">{t`Synchronize group membership with your SSO`}</h3>
+          <p className="mb4 mt1 text-medium">
+            {t`To enable this, you'll need to create mappings to tell Metabase which group(s) your users should
+               be added to based on the SSO group they're in.`}
+          </p>
+          <FormField
+            {...settingField("saml-group-sync")}
+            title={t`Synchronize group memberships`}
+            type={({ field: { value, onChange } }) => (
+              <GroupMappingsWidget
+                // map to legacy setting props
+                setting={{ key: "saml-group-sync", value }}
+                onChange={onChange}
+                settingValues={settingValues}
+                onChangeSetting={(key, value) =>
+                  updateSettings({ [key]: value })
+                }
+                mappingSetting="saml-group-mappings"
+                groupHeading={t`Group Name`}
+                groupPlaceholder={t`Group Name`}
+              />
+            )}
+          />
+          <FormField
+            {...settingField("saml-attribute-group")}
+            title={t`Group attribute name`}
+          />
+        </Box>
+
+        <div>
+          <FormMessage />
+        </div>
+        <div>
+          <FormSubmit>{t`Save changes`}</FormSubmit>
+        </div>
+      </Form>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/auth/index.js b/enterprise/frontend/src/metabase-enterprise/auth/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..d612ef710f7a28c5b6534f1358db1535c6c461af
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/auth/index.js
@@ -0,0 +1,263 @@
+import React from "react";
+import ExternalLink from "metabase/components/ExternalLink";
+import { t, jt } from "ttag";
+import { updateIn } from "icepick";
+
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import MetabaseSettings from "metabase/lib/settings";
+import {
+  PLUGIN_AUTH_PROVIDERS,
+  PLUGIN_SHOW_CHANGE_PASSWORD_CONDITIONS,
+  PLUGIN_ADMIN_SETTINGS_UPDATES,
+} from "metabase/plugins";
+import { UtilApi } from "metabase/services";
+
+import AuthenticationOption from "metabase/admin/settings/components/widgets/AuthenticationOption";
+import GroupMappingsWidget from "metabase/admin/settings/components/widgets/GroupMappingsWidget";
+import SecretKeyWidget from "metabase/admin/settings/components/widgets/SecretKeyWidget";
+
+import SettingsSAMLForm from "./components/SettingsSAMLForm";
+import SettingsJWTForm from "./components/SettingsJWTForm";
+
+import SSOButton from "./components/SSOButton";
+
+PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections =>
+  updateIn(sections, ["authentication", "settings"], settings => [
+    ...settings,
+    {
+      authName: t`SAML`,
+      authDescription: t`Allows users to login via a SAML Identity Provider.`,
+      authType: "saml",
+      authEnabled: settings => settings["saml-enabled"],
+      widget: AuthenticationOption,
+      getHidden: () => !hasPremiumFeature("sso"),
+    },
+    {
+      authName: t`JWT`,
+      authDescription: t`Allows users to login via a JWT Identity Provider.`,
+      authType: "jwt",
+      authEnabled: settings => settings["jwt-enabled"],
+      widget: AuthenticationOption,
+      getHidden: () => !hasPremiumFeature("sso"),
+    },
+    {
+      key: "enable-password-login",
+      display_name: t`Enable Password Authentication`,
+      description: t`When enabled, users can additionally log in with email and password.`,
+      type: "boolean",
+      getHidden: settings =>
+        !settings["google-auth-client-id"] &&
+        !settings["ldap-enabled"] &&
+        !settings["saml-enabled"] &&
+        !settings["jwt-enabled"],
+    },
+    {
+      key: "send-new-sso-user-admin-email?",
+      display_name: t`Notify admins of new SSO users`,
+      description: t`When enabled, administrators will receive an email the first time a user uses Single Sign-On.`,
+      type: "boolean",
+      getHidden: settings =>
+        !settings["google-auth-client-id"] &&
+        !settings["ldap-enabled"] &&
+        !settings["saml-enabled"] &&
+        !settings["jwt-enabled"],
+    },
+  ]),
+);
+
+PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
+  ...sections,
+  "authentication/saml": {
+    sidebar: false,
+    component: SettingsSAMLForm,
+    settings: [
+      {
+        key: "saml-enabled",
+        display_name: t`SAML Authentication`,
+        description: jt`Use the settings below to configure your SSO via SAML. If you have any questions, check out our ${(
+          <ExternalLink
+            href={MetabaseSettings.docsUrl(
+              "enterprise-guide/authenticating-with-saml",
+            )}
+          >
+            {t`documentation`}
+          </ExternalLink>
+        )}.`,
+        type: "boolean",
+      },
+      {
+        key: "saml-identity-provider-uri",
+        display_name: t`SAML Identity Provider SSO URL`,
+        placeholder: "https://example.com/app/my_saml_app/abc123/sso/saml",
+        type: "string",
+        required: true,
+        autoFocus: true,
+      },
+      {
+        key: "saml-identity-provider-issuer",
+        display_name: t`SAML Identity Provider Issuer`,
+        placeholder: "http://www.example.com/abc123",
+        type: "string",
+        required: false,
+      },
+      {
+        key: "saml-identity-provider-certificate",
+        display_name: t`SAML Identity Provider Certificate`,
+        type: "text",
+        required: true,
+      },
+      {
+        key: "saml-application-name",
+        display_name: t`SAML Application Name`,
+        type: "string",
+      },
+      {
+        key: "saml-keystore-path",
+        display_name: t`SAML Keystore Path`,
+        type: "string",
+      },
+      {
+        key: "saml-keystore-password",
+        display_name: t`SAML Keystore Password`,
+        placeholder: "Shh...",
+        type: "password",
+      },
+      {
+        key: "saml-keystore-alias",
+        display_name: t`SAML Keystore Alias`,
+        type: "string",
+      },
+      {
+        key: "saml-attribute-email",
+        display_name: t`Email attribute`,
+        type: "string",
+      },
+      {
+        key: "saml-attribute-firstname",
+        display_name: t`First name attribute`,
+        type: "string",
+      },
+      {
+        key: "saml-attribute-lastname",
+        display_name: t`Last name attribute`,
+        type: "string",
+      },
+      {
+        key: "saml-group-sync",
+        display_name: t`Synchronize group memberships`,
+        description: null,
+        widget: GroupMappingsWidget,
+        props: {
+          mappingSetting: "saml-group-mappings",
+          groupHeading: t`Group Name`,
+          groupPlaceholder: "Group Name",
+        },
+      },
+      {
+        key: "saml-attribute-group",
+        display_name: t`Group attribute name`,
+        type: "string",
+      },
+      {
+        key: "saml-group-mappings",
+      },
+    ],
+  },
+  "authentication/jwt": {
+    sidebar: false,
+    component: SettingsJWTForm,
+    settings: [
+      {
+        key: "jwt-enabled",
+        description: null,
+        getHidden: settings => settings["jwt-enabled"],
+        onChanged: async (
+          oldValue,
+          newValue,
+          settingsValues,
+          onChangeSetting,
+        ) => {
+          // Generate a secret key if none already exists
+          if (!oldValue && newValue && !settingsValues["jwt-shared-secret"]) {
+            const result = await UtilApi.random_token();
+            await onChangeSetting("jwt-shared-secret", result.token);
+          }
+        },
+      },
+      {
+        key: "jwt-enabled",
+        display_name: t`JWT Authentication`,
+        type: "boolean",
+        getHidden: settings => !settings["jwt-enabled"],
+      },
+      {
+        key: "jwt-identity-provider-uri",
+        display_name: t`JWT Identity Provider URI`,
+        placeholder: "https://jwt.yourdomain.org",
+        type: "string",
+        required: true,
+        autoFocus: true,
+        getHidden: settings => !settings["jwt-enabled"],
+      },
+      {
+        key: "jwt-shared-secret",
+        display_name: t`String used by the JWT signing key`,
+        type: "text",
+        required: true,
+        widget: SecretKeyWidget,
+        getHidden: settings => !settings["jwt-enabled"],
+      },
+      {
+        key: "jwt-attribute-email",
+        display_name: t`Email attribute`,
+        type: "string",
+      },
+      {
+        key: "jwt-attribute-firstname",
+        display_name: t`First name attribute`,
+        type: "string",
+      },
+      {
+        key: "jwt-attribute-lastname",
+        display_name: t`Last name attribute`,
+        type: "string",
+      },
+      {
+        key: "jwt-group-sync",
+        display_name: t`Synchronize group memberships`,
+        description: null,
+        widget: GroupMappingsWidget,
+        props: {
+          mappingSetting: "jwt-group-mappings",
+          groupHeading: t`Group Name`,
+          groupPlaceholder: "Group Name",
+        },
+      },
+      {
+        key: "jwt-group-mappings",
+      },
+    ],
+  },
+}));
+
+const SSO_PROVIDER = {
+  name: "sso",
+  Button: SSOButton,
+};
+
+PLUGIN_AUTH_PROVIDERS.push(providers => {
+  if (MetabaseSettings.get("other-sso-configured?")) {
+    providers = [SSO_PROVIDER, ...providers];
+  }
+  if (!MetabaseSettings.get("enable-password-login")) {
+    providers = providers.filter(p => p.name !== "password");
+  }
+  return providers;
+});
+
+PLUGIN_SHOW_CHANGE_PASSWORD_CONDITIONS.push(
+  user =>
+    !user.google_auth &&
+    !user.ldap_auth &&
+    MetabaseSettings.get("enable-password-login"),
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/embedding/index.js b/enterprise/frontend/src/metabase-enterprise/embedding/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..39ebd951e4d003c45660969bb22229c0bd70e7e8
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/embedding/index.js
@@ -0,0 +1,54 @@
+import React from "react";
+import { jt, t } from "ttag";
+import { updateIn } from "icepick";
+
+import MetabaseSettings from "metabase/lib/settings";
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import { PLUGIN_ADMIN_SETTINGS_UPDATES } from "metabase/plugins";
+
+import EmbeddingLevel from "metabase/admin/settings/components/widgets/EmbeddingLevel";
+
+if (hasPremiumFeature("embedding")) {
+  MetabaseSettings.hideEmbedBranding = () => true;
+}
+
+const APP_ORIGIN_SETTING = {
+  key: "embedding-app-origin",
+  display_name: t`Embedding the entire Metabase app`,
+  description: jt`If you want to embed all of Metabase, enter the origins of the websites or web apps where you want to allow embedding in an iframe, separated by a space. Here are the ${(
+    <a
+      href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors"
+      className="link"
+    >
+      exact specifications
+    </a>
+  )} for what can be entered.`,
+  placeholder: "https://*.example.com",
+  type: "string",
+  getHidden: settings => !settings["enable-embedding"],
+};
+
+PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections =>
+  updateIn(
+    sections,
+    ["embedding_in_other_applications", "settings"],
+    settings => {
+      // remove the embedding level widget from EE
+      settings = settings.filter(s => s.widget !== EmbeddingLevel);
+
+      // insert the app origin setting right after the secret key widget
+      const itemIndex = settings.findIndex(
+        s => s.key === "embedding-secret-key",
+      );
+      const sliceIndex = itemIndex === -1 ? settings.length : itemIndex + 1;
+
+      settings = [
+        ...settings.slice(0, sliceIndex),
+        APP_ORIGIN_SETTING,
+        ...settings.slice(sliceIndex),
+      ];
+
+      return settings;
+    },
+  ),
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/plugins.js b/enterprise/frontend/src/metabase-enterprise/plugins.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac83c94d8473334d40d9507f264d04a88490c9cb
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/plugins.js
@@ -0,0 +1,21 @@
+import MetabaseSettings from "metabase/lib/settings";
+
+// SETTINGS OVERRIDES:
+
+// NOTE: temporarily use "latest" for Enterprise Edition docs
+MetabaseSettings.docsTag = () => "latest";
+// NOTE: use the "enterprise" key from version-info.json
+MetabaseSettings.versionInfo = () =>
+  MetabaseSettings.get("version-info", {}).enterprise || {};
+
+// PLUGINS:
+
+// import "./management";
+
+import "./audit_app";
+import "./sandboxes";
+import "./auth";
+import "./whitelabel";
+import "./embedding";
+import "./store";
+import "./snippets";
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/components/GTAPModal.jsx b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/GTAPModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..63114e751159d7d617a1e9642c246fef1fb254f8
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/GTAPModal.jsx
@@ -0,0 +1,503 @@
+/* @flow */
+
+import React from "react";
+
+import { withRouter } from "react-router";
+import { connect } from "react-redux";
+import { push } from "react-router-redux";
+
+import MappingEditor from "./MappingEditor";
+
+import QuestionPicker from "metabase/containers/QuestionPicker";
+import QuestionParameterTargetWidget from "../containers/QuestionParameterTargetWidget";
+import Button from "metabase/components/Button";
+import ActionButton from "metabase/components/ActionButton";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import Select, { Option } from "metabase/components/Select";
+import Radio from "metabase/components/Radio";
+import Icon from "metabase/components/Icon";
+import Tooltip from "metabase/components/Tooltip";
+import { GTAPApi } from "metabase/services";
+import RetinaImage from "react-retina-image";
+
+import EntityObjectLoader from "metabase/entities/containers/EntityObjectLoader";
+import QuestionLoader from "metabase/containers/QuestionLoader";
+
+import Dimension from "metabase-lib/lib/Dimension";
+
+import _ from "underscore";
+import { jt, t } from "ttag";
+
+const mapStateToProps = () => ({});
+const mapDispatchToProps = {
+  push,
+};
+
+type GTAP = {
+  table_id: ?number,
+  group_id: ?number,
+  card_id: ?number,
+  attribute_remappings: { [attribute: string]: any },
+};
+
+type Props = {
+  params: { [name: string]: string },
+  push: (url: string) => void,
+};
+type State = {
+  gtap: ?GTAP,
+  attributesOptions: ?(string[]),
+  simple: boolean,
+};
+
+@withRouter
+@connect(
+  mapStateToProps,
+  mapDispatchToProps,
+)
+export default class GTAPModal extends React.Component {
+  props: Props;
+  state: State = {
+    gtap: null,
+    attributesOptions: null,
+    simple: true,
+  };
+  // $FlowFixMe: componentWillMount expected to return void
+  async componentWillMount() {
+    const { params } = this.props;
+
+    GTAPApi.attributes().then(attributesOptions =>
+      this.setState({ attributesOptions }),
+    );
+
+    const groupId = parseInt(params.groupId);
+    const tableId = parseInt(params.tableId);
+    const gtaps = await GTAPApi.list();
+    let gtap = _.findWhere(gtaps, { group_id: groupId, table_id: tableId });
+    if (!gtap) {
+      gtap = {
+        table_id: tableId,
+        group_id: groupId,
+        card_id: null,
+        attribute_remappings: { "": null },
+      };
+    }
+    if (Object.keys(gtap.attribute_remappings).length === 0) {
+      gtap.attribute_remappings = { "": null };
+    }
+    this.setState({ gtap, simple: gtap.card_id == null });
+  }
+
+  close = () => {
+    const {
+      push,
+      params: { databaseId, schemaName },
+    } = this.props;
+    push(
+      `/admin/permissions/databases/${databaseId}` +
+        (schemaName ? `/schemas/${encodeURIComponent(schemaName)}` : ``) +
+        `/tables`,
+    );
+  };
+
+  _getCanonicalGTAP() {
+    const { gtap, simple } = this.state;
+    if (!gtap) {
+      return null;
+    }
+    return {
+      ...gtap,
+      card_id: simple ? null : gtap.card_id,
+      attribute_remappings: _.pick(
+        gtap.attribute_remappings,
+        (value, key) => value && key,
+      ),
+    };
+  }
+
+  save = async () => {
+    const gtap = this._getCanonicalGTAP();
+    if (!gtap) {
+      throw new Error("No GTAP");
+    }
+    if (gtap.id != null) {
+      await GTAPApi.update(gtap);
+    } else {
+      await GTAPApi.create(gtap);
+    }
+    this.close();
+  };
+
+  isValid() {
+    const gtap = this._getCanonicalGTAP();
+    const { simple } = this.state;
+    if (!gtap) {
+      return false;
+    } else if (simple) {
+      return Object.entries(gtap.attribute_remappings).length > 0;
+    } else {
+      return gtap.card_id != null;
+    }
+  }
+
+  render() {
+    const { gtap, simple, attributesOptions } = this.state;
+
+    const valid = this.isValid();
+    const canonicalGTAP = this._getCanonicalGTAP();
+
+    const remainingAttributesOptions =
+      gtap && attributesOptions
+        ? attributesOptions.filter(
+            attribute => !(attribute in gtap.attribute_remappings),
+          )
+        : [];
+
+    const hasAttributesOptions =
+      attributesOptions && attributesOptions.length > 0;
+    const hasValidMappings =
+      Object.keys((canonicalGTAP || {}).attribute_remappings || {}).length > 0;
+
+    return (
+      <div>
+        <h2 className="p3">{t`Grant sandboxed access to this table`}</h2>
+        <LoadingAndErrorWrapper loading={!gtap}>
+          {() =>
+            gtap && (
+              <div>
+                <div className="px3 pb3">
+                  <div className="pb3">
+                    {t`When users in this group view this table they'll see a version of it that's filtered by their user attributes, or a custom view of it based on a saved question.`}
+                  </div>
+                  <h4 className="pb1">
+                    {t`How do you want to filter this table for users in this group?`}
+                  </h4>
+                  <Radio
+                    value={simple}
+                    options={[
+                      { name: "Filter by a column in the table", value: true },
+                      {
+                        name:
+                          "Use a saved question to create a custom view for this table",
+                        value: false,
+                      },
+                    ]}
+                    onChange={simple => this.setState({ simple })}
+                    vertical
+                  />
+                </div>
+                {!simple && (
+                  <div className="px3 pb3">
+                    <div className="pb2">
+                      {t`Pick a saved question that returns the custom view of this table that these users should see.`}
+                    </div>
+                    <QuestionPicker
+                      value={gtap.card_id}
+                      onChange={card_id =>
+                        this.setState({ gtap: { ...gtap, card_id } })
+                      }
+                    />
+                  </div>
+                )}
+                {gtap &&
+                  attributesOptions &&
+                  // show if in simple mode, or the admin has selected a card
+                  (simple || gtap.card_id != null) &&
+                  (hasAttributesOptions || hasValidMappings ? (
+                    <div className="p3 border-top border-bottom">
+                      {!simple && (
+                        <div className="pb2">
+                          {t`You can optionally add additional filters here based on user attributes. These filters will be applied on top of any filters that are already in this saved question.`}
+                        </div>
+                      )}
+                      <AttributeMappingEditor
+                        value={gtap.attribute_remappings}
+                        onChange={attribute_remappings =>
+                          this.setState({
+                            gtap: { ...gtap, attribute_remappings },
+                          })
+                        }
+                        simple={simple}
+                        gtap={gtap}
+                        attributesOptions={remainingAttributesOptions}
+                      />
+                    </div>
+                  ) : (
+                    <div className="px3">
+                      <AttributeOptionsEmptyState
+                        title={
+                          simple
+                            ? t`For this option to work, your users need to have some attributes`
+                            : t`To add additional filters, your users need to have some attributes`
+                        }
+                      />
+                    </div>
+                  ))}
+              </div>
+            )
+          }
+        </LoadingAndErrorWrapper>
+        <div className="p3">
+          {valid && canonicalGTAP && (
+            <div className="pb1">
+              <GTAPSummary gtap={canonicalGTAP} />
+            </div>
+          )}
+          <div className="flex align-center justify-end">
+            <Button onClick={this.close}>{t`Cancel`}</Button>
+            <ActionButton
+              className="ml1"
+              actionFn={this.save}
+              primary
+              disabled={!valid}
+            >
+              {t`Save`}
+            </ActionButton>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+const AttributePicker = ({ value, onChange, attributesOptions }) => (
+  <div style={{ minWidth: 200 }}>
+    <Select
+      value={value}
+      onChange={e => onChange(e.target.value)}
+      placeholder={
+        attributesOptions.length === 0
+          ? t`No user attributes`
+          : t`Pick a user attribute`
+      }
+      disabled={attributesOptions.length === 0}
+    >
+      {attributesOptions.map(attributesOption => (
+        <Option key={attributesOption} value={attributesOption}>
+          {attributesOption}
+        </Option>
+      ))}
+    </Select>
+  </div>
+);
+
+const QuestionTargetPicker = ({ value, onChange, questionId }) => (
+  <div style={{ minWidth: 200 }}>
+    <QuestionParameterTargetWidget
+      questionId={questionId}
+      target={value}
+      onChange={onChange}
+      placeholder={t`Pick a parameter`}
+    />
+  </div>
+);
+
+const rawDataQuestionForTable = tableId => ({
+  dataset_query: {
+    type: "query",
+    query: { "source-table": tableId },
+  },
+});
+
+const TableTargetPicker = ({ value, onChange, tableId }) => (
+  <div style={{ minWidth: 200 }}>
+    <QuestionParameterTargetWidget
+      questionObject={rawDataQuestionForTable(tableId)}
+      target={value}
+      onChange={onChange}
+      placeholder={t`Pick a column`}
+    />
+  </div>
+);
+
+const SummaryRow = ({ icon, content }) => (
+  <div className="flex align-center">
+    <Icon className="p1" name={icon} />
+    <span>{content}</span>
+  </div>
+);
+
+const GTAPSummary = ({ gtap }: { gtap: GTAP }) => {
+  return (
+    <div>
+      <div className="px1 pb2 text-uppercase text-small text-grey-4">
+        Summary
+      </div>
+      <SummaryRow
+        icon="group"
+        content={jt`Users in ${<GroupName groupId={gtap.group_id} />} can view`}
+      />
+      <SummaryRow
+        icon="table"
+        content={
+          gtap.card_id
+            ? jt`rows in the ${(
+                <QuestionName questionId={gtap.card_id} />
+              )} question`
+            : jt`rows in the ${<TableName tableId={gtap.table_id} />} table`
+        }
+      />
+      {Object.entries(gtap.attribute_remappings).map(
+        ([attribute, target], index) => (
+          <SummaryRow
+            key={attribute}
+            icon="funneloutline"
+            content={
+              index === 0
+                ? jt`where ${(
+                    <TargetName gtap={gtap} target={target} />
+                  )} equals ${<span className="text-code">{attribute}</span>}`
+                : jt`and ${(
+                    <TargetName gtap={gtap} target={target} />
+                  )} equals ${<span className="text-code">{attribute}</span>}`
+            }
+          />
+        ),
+      )}
+    </div>
+  );
+};
+
+// TODO: use EntityName component
+const GroupName = ({ groupId }) => (
+  <EntityObjectLoader
+    entityType="groups"
+    entityId={groupId}
+    properties={["name"]}
+    loadingAndErrorWrapper={false}
+  >
+    {({ object }) => <strong>{object && object.name}</strong>}
+  </EntityObjectLoader>
+);
+
+// TODO: use EntityName component
+const QuestionName = ({ questionId }) => (
+  <EntityObjectLoader
+    entityType="questions"
+    entityId={questionId}
+    properties={["name"]}
+    loadingAndErrorWrapper={false}
+  >
+    {({ object }) => <strong>{object && object.name}</strong>}
+  </EntityObjectLoader>
+);
+
+// TODO: use EntityName component
+const TableName = ({ tableId }) => (
+  <EntityObjectLoader
+    entityType="tables"
+    entityId={tableId}
+    properties={["display_name"]}
+    loadingAndErrorWrapper={false}
+  >
+    {({ object }) => <strong>{object && object.display_name}</strong>}
+  </EntityObjectLoader>
+);
+
+const TargetName = ({ gtap, target }: { gtap: GTAP, target: any }) => {
+  if (Array.isArray(target)) {
+    if (
+      (target[0] === "variable" || target[0] === "dimension") &&
+      target[1][0] === "template-tag"
+    ) {
+      return (
+        <span>
+          <strong>{target[1][1]}</strong> variable
+        </span>
+      );
+    } else if (target[0] === "dimension") {
+      return (
+        <QuestionLoader
+          questionId={gtap.card_id}
+          questionObject={
+            gtap.card_id == null ? rawDataQuestionForTable(gtap.table_id) : null
+          }
+        >
+          {({ question }) =>
+            question && (
+              <span>
+                <strong>
+                  {Dimension.parseMBQL(target[1], question.metadata()).render()}
+                </strong>{" "}
+                field
+              </span>
+            )
+          }
+        </QuestionLoader>
+      );
+    }
+  }
+  return <emphasis>[Unknown target]</emphasis>;
+};
+
+const AttributeOptionsEmptyState = ({ title }) => (
+  <div className="flex align-center rounded bg-slate-extra-light p2">
+    <RetinaImage
+      src="app/assets/img/attributes_illustration.png"
+      className="mr2"
+    />
+    <div>
+      <h3 className="pb1">{title}</h3>
+      <div>{t`You can add attributes automatically by setting up an SSO that uses SAML, or you can enter them manually by going to the People section and clicking on the … menu on the far right.`}</div>
+    </div>
+  </div>
+);
+
+const AttributeMappingEditor = ({
+  value,
+  onChange,
+  simple,
+  attributesOptions,
+  gtap,
+}) => (
+  <MappingEditor
+    style={{ width: "100%" }}
+    value={value}
+    onChange={onChange}
+    keyPlaceholder={t`Pick a user attribute`}
+    keyHeader={
+      <div className="text-uppercase text-small text-grey-4 flex align-center">
+        {t`User attribute`}
+        <Tooltip
+          tooltip={t`We can automatically get your users’ attributes if you’ve set up SSO, or you can add them manually from the "…" menu in the People section of the Admin Panel.`}
+        >
+          <Icon className="ml1" name="infooutlined" />
+        </Tooltip>
+      </div>
+    }
+    renderKeyInput={({ value, onChange }) => (
+      <AttributePicker
+        value={value}
+        onChange={onChange}
+        attributesOptions={(value ? [value] : []).concat(attributesOptions)}
+      />
+    )}
+    render
+    valuePlaceholder={simple ? t`Pick a column` : t`Pick a parameter`}
+    valueHeader={
+      <div className="text-uppercase text-small text-grey-4">
+        {simple ? t`Column` : t`Parameter or variable`}
+      </div>
+    }
+    renderValueInput={({ value, onChange }) =>
+      simple && gtap.table_id != null ? (
+        <TableTargetPicker
+          value={value}
+          onChange={onChange}
+          tableId={gtap.table_id}
+        />
+      ) : !simple && gtap.card_id != null ? (
+        <QuestionTargetPicker
+          value={value}
+          onChange={onChange}
+          questionId={gtap.card_id}
+        />
+      ) : null
+    }
+    divider={<span className="px2 text-bold">{t`equals`}</span>}
+    addText={t`Add a filter`}
+    canAdd={attributesOptions.length > 0}
+    canDelete={true}
+    swapKeyAndValue
+  />
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/components/LoginAttributesWidget.jsx b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/LoginAttributesWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e76153c10f596ff6340b2cc206058150c79b6bc2
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/LoginAttributesWidget.jsx
@@ -0,0 +1,28 @@
+/* @flow */
+
+import React from "react";
+
+import MappingEditor from "./MappingEditor";
+
+type LoginAttributes = {
+  [key: string]: string,
+};
+
+type FormField<T> = {
+  value: T,
+  onChange: (value: T) => void,
+};
+
+type Props = {
+  field: FormField<?LoginAttributes>,
+};
+
+const LoginAttributesWidget = ({ field }: Props) => (
+  <MappingEditor
+    value={field.value || {}}
+    onChange={field.onChange}
+    addText="Add an attribute"
+  />
+);
+
+export default LoginAttributesWidget;
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/components/MappingEditor.jsx b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/MappingEditor.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f9c1e76c2c5a731cd8a4a8799673e4aa9cdc897c
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/components/MappingEditor.jsx
@@ -0,0 +1,154 @@
+/* @flow */
+
+import React from "react";
+
+import Button from "metabase/components/Button";
+
+import _ from "underscore";
+
+type Mapping = {
+  [key: string]: any,
+};
+
+type Style = {
+  [key: string]: any,
+};
+
+type Props = {
+  value: Mapping,
+  onChange: (mapping: Mapping) => void,
+  className?: string,
+  style?: Style,
+  keyPlaceholder?: string,
+  valuePlaceholder?: string,
+  keyHeader?: React.Element<any>,
+  valueHeader?: React.Element<any>,
+  divider?: React.Element<any>,
+  canAdd?: boolean,
+  canDelete?: boolean,
+  addText?: string,
+  swapKeyAndValue?: boolean,
+  renderKeyInput?: any, // FIXME
+  renderValueInput?: any, // FIXME
+};
+
+const DefaultRenderInput = ({ value, onChange, placeholder }) => (
+  <input
+    className="input"
+    value={value}
+    placeholder={placeholder}
+    onChange={e => onChange(e.target.value)}
+  />
+);
+
+const MappingEditor = ({
+  value,
+  onChange,
+  className,
+  style,
+  keyHeader,
+  valueHeader,
+  keyPlaceholder = "Key",
+  valuePlaceholder = "Value",
+  renderKeyInput = DefaultRenderInput,
+  renderValueInput = DefaultRenderInput,
+  divider,
+  canAdd = true,
+  canDelete = true,
+  addText = "Add",
+  swapKeyAndValue,
+}: Props) => {
+  const mapping = value;
+  const entries = Object.entries(mapping);
+  return (
+    <table className={className} style={style}>
+      {keyHeader || valueHeader ? (
+        <thead>
+          <tr>
+            <td>{!swapKeyAndValue ? keyHeader : valueHeader}</td>
+            <td />
+            <td>{!swapKeyAndValue ? valueHeader : keyHeader}</td>
+          </tr>
+        </thead>
+      ) : null}
+      <tbody>
+        {entries.map(([key, value], index) => {
+          const keyInput = renderKeyInput({
+            value: key,
+            placeholder: keyPlaceholder,
+            onChange: newKey =>
+              onChange(replaceMappingKey(mapping, key, newKey)),
+          });
+          const valueInput = renderValueInput({
+            value: value,
+            placeholder: valuePlaceholder,
+            onChange: newValue =>
+              onChange(replaceMappingValue(mapping, key, newValue)),
+          });
+          return (
+            <tr key={index}>
+              <td className="pb1">
+                {!swapKeyAndValue ? keyInput : valueInput}
+              </td>
+              <td className="pb1 px1">{divider}</td>
+              <td className="pb1">
+                {!swapKeyAndValue ? valueInput : keyInput}
+              </td>
+              {canDelete && (
+                <td>
+                  <Button
+                    icon="close"
+                    type="button" // prevent submit. should be the default but it's not
+                    borderless
+                    onClick={() => onChange(removeMapping(mapping, key))}
+                  />
+                </td>
+              )}
+            </tr>
+          );
+        })}
+        {!("" in mapping) &&
+          _.every(mapping, value => value != null) &&
+          canAdd && (
+            <tr>
+              <td colSpan={2}>
+                <Button
+                  icon="add"
+                  type="button" // prevent submit. should be the default but it's not
+                  borderless
+                  className="text-brand p0 py1"
+                  onClick={() => onChange(addMapping(mapping))}
+                >
+                  {addText}
+                </Button>
+              </td>
+            </tr>
+          )}
+      </tbody>
+    </table>
+  );
+};
+
+const addMapping = mappings => {
+  return { ...mappings, "": null };
+};
+
+const removeMapping = (mappings, prevKey) => {
+  mappings = { ...mappings };
+  delete mappings[prevKey];
+  return mappings;
+};
+
+const replaceMappingValue = (mappings, oldKey, newValue) => {
+  return { ...mappings, [oldKey]: newValue };
+};
+
+const replaceMappingKey = (mappings, oldKey, newKey) => {
+  const newMappings = {};
+  for (const key in mappings) {
+    newMappings[key === oldKey ? newKey : key] = mappings[key];
+  }
+  return newMappings;
+};
+
+export default MappingEditor;
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/containers/QuestionParameterTargetWidget.jsx b/enterprise/frontend/src/metabase-enterprise/sandboxes/containers/QuestionParameterTargetWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fdedafcd80ed3c52d7666c4556275e7e98807c9b
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/containers/QuestionParameterTargetWidget.jsx
@@ -0,0 +1,36 @@
+/* @flow */
+
+import React from "react";
+
+import ParameterTargetWidget from "metabase/parameters/components/ParameterTargetWidget";
+import { QuestionLoaderHOC } from "metabase/containers/QuestionLoader";
+
+import * as Dashboard from "metabase/meta/Dashboard";
+
+import type { ParameterTarget } from "metabase-types/types/Parameter";
+
+type Props = {
+  questionObject?: any, // FIXME: minimal card
+  questionId?: number,
+  questionHash?: string,
+  target: ?ParameterTarget,
+  onChange: (target: ?ParameterTarget) => void,
+};
+
+@QuestionLoaderHOC
+export default class QuestionParameterTargetWidget extends React.Component {
+  props: Props;
+
+  render() {
+    // $FlowFixMe: question provided by HOC
+    const { question, ...props } = this.props;
+    const mappingOptions = question
+      ? Dashboard.getParameterMappingOptions(
+          question.metadata(),
+          null,
+          question.card(),
+        )
+      : [];
+    return <ParameterTargetWidget {...props} mappingOptions={mappingOptions} />;
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/sandboxes/index.js b/enterprise/frontend/src/metabase-enterprise/sandboxes/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd73c894d2e2fb75b8f721d81d0f8a09a89b5be6
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/sandboxes/index.js
@@ -0,0 +1,73 @@
+import {
+  PLUGIN_ADMIN_USER_FORM_FIELDS,
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_ROUTES,
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_OPTIONS,
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS,
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_POST_ACTION,
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_PERMISSION_VALUE,
+} from "metabase/plugins";
+
+import React from "react";
+import { push } from "react-router-redux";
+import { t } from "ttag";
+
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import { color, alpha } from "metabase/lib/colors";
+
+import { ModalRoute } from "metabase/hoc/ModalRoute";
+import LoginAttributesWidget from "./components/LoginAttributesWidget";
+import GTAPModal from "./components/GTAPModal";
+
+const OPTION_BLUE = {
+  iconColor: color("brand"),
+  bgColor: alpha(color("brand"), 0.15),
+};
+
+const OPTION_SEGMENTED = {
+  ...OPTION_BLUE,
+  value: "controlled",
+  title: t`Grant sandboxed access`,
+  tooltip: t`Sandboxed access`,
+  icon: "permissions_limited",
+};
+
+const getEditSegementedAccessUrl = (
+  groupId,
+  { databaseId, schemaName, tableId },
+) =>
+  `/admin/permissions` +
+  `/databases/${databaseId}` +
+  (schemaName ? `/schemas/${encodeURIComponent(schemaName)}` : "") +
+  `/tables/${tableId}/segmented/group/${groupId}`;
+
+const getEditSegementedAccessAction = (groupId, entityId) => ({
+  ...OPTION_BLUE,
+  title: t`Edit sandboxed access`,
+  icon: "pencil",
+  value: push(getEditSegementedAccessUrl(groupId, entityId)),
+});
+
+const getEditSegmentedAcessPostAction = (groupId, entityId) =>
+  push(getEditSegementedAccessUrl(groupId, entityId));
+
+if (hasPremiumFeature("sandboxes")) {
+  PLUGIN_ADMIN_USER_FORM_FIELDS.push({
+    name: "login_attributes",
+    title: "Attributes",
+    type: LoginAttributesWidget,
+  });
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_ROUTES.push(
+    <ModalRoute path=":tableId/segmented/group/:groupId" modal={GTAPModal} />,
+  );
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_OPTIONS.push(OPTION_SEGMENTED);
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS["controlled"].push(
+    getEditSegementedAccessAction,
+  );
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_POST_ACTION[
+    "controlled"
+  ] = getEditSegmentedAcessPostAction;
+  PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_PERMISSION_VALUE["controlled"] = {
+    read: "all",
+    query: "segmented",
+  };
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/settings/index.js b/enterprise/frontend/src/metabase-enterprise/settings/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e243d017c80000e010c6f4bb8438e39ae912a0f5
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/settings/index.js
@@ -0,0 +1,9 @@
+import MetabaseSettings from "metabase/lib/settings";
+
+export function hasPremiumFeature(feature) {
+  const hasFeature = MetabaseSettings.get("premium-features", {})[feature];
+  if (hasFeature == null) {
+    console.warn("Unknown premium feature", feature);
+  }
+  return hasFeature;
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/settings/selectors.js b/enterprise/frontend/src/metabase-enterprise/settings/selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..e63797c361eb27b8c68de03942fc8694fa8f16d6
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/settings/selectors.js
@@ -0,0 +1,27 @@
+import { createSelector } from "reselect";
+
+const DEFAULT_LOGO_URL = "app/assets/img/logo.svg";
+
+export const getLogoUrl = state =>
+  state.settings.values["application-logo-url"] ||
+  state.settings.values.application_logo_url ||
+  DEFAULT_LOGO_URL;
+
+const getApplicationColors = state =>
+  state.settings.values["application-colors"] ||
+  state.settings.values.application_colors;
+
+const getHasCustomColors = createSelector(
+  [getApplicationColors],
+  applicationColors => Object.keys(applicationColors || {}).length > 0,
+);
+
+export const getHasCustomLogo = createSelector(
+  [getLogoUrl],
+  logoUrl => logoUrl !== DEFAULT_LOGO_URL,
+);
+
+export const getIsWhitelabeled = createSelector(
+  [getHasCustomLogo, getHasCustomColors],
+  (hasCustomLogo, hasCustomColors) => hasCustomLogo || hasCustomColors,
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..35bbc25b8d8f2b04f65f51bd91855d2fe32717c5
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx
@@ -0,0 +1,91 @@
+import React from "react";
+import { t } from "ttag";
+
+import MetabaseSettings from "metabase/lib/settings";
+import { canonicalCollectionId } from "metabase/entities/collections";
+import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
+import AccordionList from "metabase/components/AccordionList";
+import Icon from "metabase/components/Icon";
+
+const ICON_SIZE = 16;
+
+export default class CollectionOptionsButton extends React.Component {
+  render() {
+    if (!MetabaseSettings.enhancementsEnabled()) {
+      return null;
+    }
+    const items = this.popoverOptions();
+    if (items.length === 0) {
+      return null;
+    }
+    const { className } = this.props;
+
+    return (
+      <div
+        className={className}
+        // prevent the ellipsis click from selecting the folder also
+        onClick={e => e.stopPropagation()}
+        // cap the large ellipsis so it doesn't increase the row height
+        style={{ height: ICON_SIZE }}
+      >
+        <PopoverWithTrigger
+          triggerElement={
+            <Icon name="ellipsis" size={20} className="hover-child" />
+          }
+        >
+          {({ onClose }) => (
+            <AccordionList
+              className="text-brand"
+              sections={[{ items }]}
+              onChange={item => {
+                item.onClick();
+                onClose();
+              }}
+            />
+          )}
+        </PopoverWithTrigger>
+      </div>
+    );
+  }
+
+  popoverOptions = () => {
+    const { collection, setSidebarState, user } = this.props;
+    if (!collection.can_write) {
+      return [];
+    }
+    if (collection.archived) {
+      return [
+        {
+          name: t`Unarchive`,
+          onClick: () => collection.setArchived(false),
+        },
+      ];
+    }
+    const onEdit = collection =>
+      setSidebarState({ modalSnippetCollection: collection });
+    const onEditCollectionPermissions = () =>
+      setSidebarState({ permissionsModalCollectionId: collection.id });
+
+    const options = [];
+    const isRoot = canonicalCollectionId(collection.id) === null;
+    if (!isRoot) {
+      options.push({
+        name: t`Edit folder details`,
+        onClick: () => onEdit(collection),
+      });
+    }
+    if (user && user.is_superuser) {
+      options.push({
+        name: t`Change permissions`,
+        onClick: onEditCollectionPermissions,
+      });
+    }
+    if (!isRoot) {
+      options.push({
+        name: t`Archive`,
+        onClick: () => collection.setArchived(true),
+      });
+    }
+    return options;
+  };
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionRow.jsx b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionRow.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5fc6818085f31dce87b72e11c258e59c84e273d3
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionRow.jsx
@@ -0,0 +1,34 @@
+import React from "react";
+import cx from "classnames";
+
+import Icon from "metabase/components/Icon";
+import SnippetCollections from "metabase/entities/snippet-collections";
+
+import CollectionOptionsButton from "./CollectionOptionsButton";
+
+const ICON_SIZE = 16;
+
+@SnippetCollections.load({ id: (state, props) => props.item.id, wrapped: true })
+export default class CollectionRow extends React.Component {
+  render() {
+    const {
+      snippetCollection: collection,
+      setSnippetCollectionId,
+    } = this.props;
+    const onSelectCollection = () => setSnippetCollectionId(collection.id);
+
+    return (
+      <div
+        className={cx(
+          { "bg-light-hover cursor-pointer": !collection.archived },
+          "hover-parent hover--visibility flex align-center py2 px3 text-brand",
+        )}
+        {...(collection.archived ? undefined : { onClick: onSelectCollection })}
+      >
+        <Icon name="folder" size={ICON_SIZE} style={{ opacity: 0.25 }} />
+        <span className="flex-full ml1 text-bold">{collection.name}</span>
+        <CollectionOptionsButton {...this.props} collection={collection} />
+      </div>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/snippets/components/SnippetCollectionModal.jsx b/enterprise/frontend/src/metabase-enterprise/snippets/components/SnippetCollectionModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..43f24e74b3f1d3dd34cd266e44b42ad69b9e5bf6
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/snippets/components/SnippetCollectionModal.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { t } from "ttag";
+
+import Modal from "metabase/components/Modal";
+
+import SnippetCollections from "metabase/entities/snippet-collections";
+
+@SnippetCollections.load({ id: (state, props) => props.collection.id })
+export default class SnippetCollectionModal extends React.Component {
+  render() {
+    const {
+      snippetCollection,
+      collection: passedCollection,
+      onClose,
+      onSaved,
+    } = this.props;
+    const collection = snippetCollection || passedCollection;
+    return (
+      <Modal onClose={onClose}>
+        <SnippetCollections.ModalForm
+          title={
+            collection.id == null
+              ? t`Create your new folder`
+              : t`Editing ${collection.name}`
+          }
+          snippetCollection={collection}
+          onClose={onClose}
+          onSaved={onSaved}
+        />
+      </Modal>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/snippets/index.js b/enterprise/frontend/src/metabase-enterprise/snippets/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd795ee973ccb6c4ffe22c744c198982c4c84887
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/snippets/index.js
@@ -0,0 +1,77 @@
+import React from "react";
+import { t } from "ttag";
+
+import {
+  PLUGIN_SNIPPET_SIDEBAR_PLUS_MENU_OPTIONS,
+  PLUGIN_SNIPPET_SIDEBAR_ROW_RENDERERS,
+  PLUGIN_SNIPPET_SIDEBAR_MODALS,
+  PLUGIN_SNIPPET_SIDEBAR_HEADER_BUTTONS,
+} from "metabase/plugins";
+
+import MetabaseSettings from "metabase/lib/settings";
+import CollectionPermissionsModal from "metabase/admin/permissions/containers/CollectionPermissionsModal";
+import Modal from "metabase/components/Modal";
+
+import CollectionRow from "./components/CollectionRow";
+import SnippetCollectionModal from "./components/SnippetCollectionModal";
+import CollectionOptionsButton from "./components/CollectionOptionsButton";
+
+if (MetabaseSettings.enhancementsEnabled()) {
+  PLUGIN_SNIPPET_SIDEBAR_PLUS_MENU_OPTIONS.push(snippetSidebar => ({
+    icon: "folder",
+    name: t`New folder`,
+    onClick: () =>
+      snippetSidebar.setState({
+        modalSnippetCollection: {
+          parent_id: snippetSidebar.props.snippetCollection.id,
+        },
+      }),
+  }));
+}
+
+PLUGIN_SNIPPET_SIDEBAR_MODALS.push(
+  snippetSidebar =>
+    snippetSidebar.state.modalSnippetCollection && (
+      <SnippetCollectionModal
+        collection={snippetSidebar.state.modalSnippetCollection}
+        onClose={() =>
+          snippetSidebar.setState({ modalSnippetCollection: null })
+        }
+        onSaved={() => {
+          snippetSidebar.setState({ modalSnippetCollection: null });
+        }}
+      />
+    ),
+  snippetSidebar =>
+    snippetSidebar.state.permissionsModalCollectionId != null && (
+      <Modal
+        onClose={() =>
+          snippetSidebar.setState({ permissionsModalCollectionId: null })
+        }
+      >
+        <CollectionPermissionsModal
+          params={{
+            collectionId: snippetSidebar.state.permissionsModalCollectionId,
+          }}
+          onClose={() =>
+            snippetSidebar.setState({ permissionsModalCollectionId: null })
+          }
+          namespace="snippets"
+        />
+      </Modal>
+    ),
+);
+
+PLUGIN_SNIPPET_SIDEBAR_ROW_RENDERERS.collection = CollectionRow;
+
+PLUGIN_SNIPPET_SIDEBAR_HEADER_BUTTONS.push((snippetSidebar, props) => {
+  const collection = snippetSidebar.props.snippetCollection;
+  return (
+    <CollectionOptionsButton
+      {...snippetSidebar.props}
+      {...props}
+      setSidebarState={snippetSidebar.setState.bind(snippetSidebar)}
+      collection={collection}
+    />
+  );
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/store/components/StoreIcon.jsx b/enterprise/frontend/src/metabase-enterprise/store/components/StoreIcon.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0d07763f5519f405d36b1125639003912da5628e
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/components/StoreIcon.jsx
@@ -0,0 +1,29 @@
+import React from "react";
+import { Flex } from "grid-styled";
+import Icon from "metabase/components/Icon";
+import colors from "metabase/lib/colors";
+
+const ICON_SIZE = 22;
+const WRAPPER_SIZE = ICON_SIZE * 2.5;
+
+const StoreIconWrapper = ({ children, color }) => (
+  <Flex
+    align="center"
+    justify="center"
+    p={2}
+    bg={color || colors["brand"]}
+    color="white"
+    w={WRAPPER_SIZE}
+    style={{ borderRadius: 99, height: WRAPPER_SIZE }}
+  >
+    {children}
+  </Flex>
+);
+
+const StoreIcon = ({ color, name, ...props }) => (
+  <StoreIconWrapper color={color}>
+    <Icon name={name} size={ICON_SIZE} />
+  </StoreIconWrapper>
+);
+
+export default StoreIcon;
diff --git a/enterprise/frontend/src/metabase-enterprise/store/containers/StoreAccount.jsx b/enterprise/frontend/src/metabase-enterprise/store/containers/StoreAccount.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..37c8f0c6df503d7b698ae95fa9d96bc1b952e5f0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/containers/StoreAccount.jsx
@@ -0,0 +1,304 @@
+import React from "react";
+import { Box, Flex } from "grid-styled";
+import { t } from "ttag";
+
+import _ from "underscore";
+
+import colors from "metabase/lib/colors";
+
+import StoreIcon from "../components/StoreIcon";
+import Card from "metabase/components/Card";
+import Link from "metabase/components/Link";
+import ExternalLink from "metabase/components/ExternalLink";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+
+import fitViewport from "metabase/hoc/FitViewPort";
+
+import moment from "moment";
+
+import FEATURES from "../lib/features";
+import { StoreApi } from "../lib/services";
+
+@fitViewport
+export default class StoreAccount extends React.Component {
+  state = {
+    status: null,
+    error: null,
+  };
+
+  async componentWillMount() {
+    try {
+      this.setState({
+        status: await StoreApi.tokenStatus(),
+      });
+    } catch (e) {
+      this.setState({
+        error: e,
+      });
+    }
+  }
+
+  render() {
+    const { status, error } = this.state;
+
+    const features =
+      status &&
+      status.features &&
+      _.object(status.features.map(f => [f, true]));
+    const expires = status && status.valid_thru && moment(status.valid_thru);
+
+    return (
+      <Flex
+        align="center"
+        justify="center"
+        flexDirection="column"
+        className={this.props.fitClassNames}
+      >
+        {error ? (
+          error.status === 404 ? (
+            <Unlicensed />
+          ) : (
+            <TokenError />
+          )
+        ) : (
+          <LoadingAndErrorWrapper loading={!status} className="full">
+            {() =>
+              status.valid && !status.trial ? (
+                <Active features={features} expires={expires} />
+              ) : !status.valid && !status.trial ? (
+                <Expired features={features} expires={expires} />
+              ) : status.valid && status.trial ? (
+                <TrialActive features={features} expires={expires} />
+              ) : !status.valid && status.trial ? (
+                <TrialExpired features={features} expires={expires} />
+              ) : (
+                <h2>{status.status}</h2>
+              )
+            }
+          </LoadingAndErrorWrapper>
+        )}
+      </Flex>
+    );
+  }
+}
+
+const TokenError = () => (
+  <Flex align="center" justify="center" flexDirection="column">
+    <h2 className="text-error">{t`We're having trouble validating your token`}</h2>
+    <h4 className="mt2">{t`Please double-check that your instance can connect to Metabase's servers`}</h4>
+    <ExternalLink
+      className="Button Button--primary mt4"
+      href="mailto:support@metabase.com"
+    >
+      {t`Get help`}
+    </ExternalLink>
+  </Flex>
+);
+
+const Unlicensed = () => (
+  <AccountStatus
+    title={t`Get even more out of Metabase with the Enterprise Edition`}
+    subtitle={
+      <h4 className="text-centered">{t`All the tools you need to quickly and easily provide reports for your customers, or to help you run and monitor Metabase in a large organization`}</h4>
+    }
+    preview
+  >
+    <Box m={4}>
+      <ExternalLink
+        className="Button Button--primary"
+        href={"http://metabase.com/enterprise/"}
+      >
+        {t`Learn more`}
+      </ExternalLink>
+      <Link className="Button ml2" to={"admin/store/activate"}>
+        {t`Activate a license`}
+      </Link>
+    </Box>
+  </AccountStatus>
+);
+
+const TrialActive = ({ features, expires }) => (
+  <AccountStatus
+    title={t`Your trial is active with these features`}
+    subtitle={expires && <h3>{t`Trial expires ${expires.fromNow()}`}</h3>}
+    features={features}
+  >
+    <CallToAction
+      title={t`Need help? Ready to buy?`}
+      buttonText={t`Talk to us`}
+      buttonLink={
+        "mailto:support@metabase.com?Subject=Metabase Enterprise Edition"
+      }
+    />
+    <Link
+      className="link"
+      to={"admin/store/activate"}
+    >{t`Activate a license`}</Link>
+  </AccountStatus>
+);
+
+const TrialExpired = ({ features }) => (
+  <AccountStatus title={t`Your trial has expired`} features={features} expired>
+    <CallToAction
+      title={t`Need more time? Ready to buy?`}
+      buttonText={t`Talk to us`}
+      buttonLink={
+        "mailto:support@metabase.com?Subject=Expired Enterprise Trial"
+      }
+    />
+    <Link
+      className="link"
+      to={"admin/store/activate"}
+    >{t`Activate a license`}</Link>
+  </AccountStatus>
+);
+
+const Active = ({ features, expires }) => (
+  <AccountStatus
+    title={t`Your features are active!`}
+    subtitle={
+      expires && (
+        <h3>{t`Your licence is valid through ${expires.format(
+          "MMMM D, YYYY",
+        )}`}</h3>
+      )
+    }
+    features={features}
+  />
+);
+
+const Expired = ({ features, expires }) => (
+  <AccountStatus
+    title={t`Your license has expired`}
+    subtitle={
+      expires && <h3>{t`It expired on ${expires.format("MMMM D, YYYY")}`}</h3>
+    }
+    features={features}
+    expired
+  >
+    <CallToAction
+      title={t`Want to renew your license?`}
+      buttonText={t`Talk to us`}
+      buttonLink={
+        "mailto:support@metabase.com?Subject=Renewing my Enterprise License"
+      }
+    />
+  </AccountStatus>
+);
+
+const AccountStatus = ({
+  title,
+  subtitle,
+  features = {},
+  expired,
+  preview,
+  children,
+  className,
+}) => {
+  // put included features first
+  const [included, notIncluded] = _.partition(
+    Object.entries(FEATURES),
+    ([id, feature]) => features[id],
+  );
+  const featuresOrdered = [...included, ...notIncluded];
+  return (
+    <Flex
+      align="center"
+      justify="center"
+      flexDirection="column"
+      className={className}
+      p={[2, 4]}
+      w="100%"
+    >
+      <Box>
+        <h2>{title}</h2>
+      </Box>
+      {subtitle && (
+        <Box mt={2} color={colors["text-medium"]} style={{ maxWidth: 500 }}>
+          {subtitle}
+        </Box>
+      )}
+      <Flex mt={4} align="center" flexWrap="wrap" w="100%">
+        {featuresOrdered.map(([id, feature]) => (
+          <Feature
+            feature={feature}
+            included={features[id]}
+            expired={expired}
+            preview={preview}
+          />
+        ))}
+      </Flex>
+      {children}
+    </Flex>
+  );
+};
+
+const CallToAction = ({ title, buttonText, buttonLink }) => (
+  <Box className="rounded bg-medium m4 py3 px4 flex flex-column layout-centered">
+    <h3 className="mb3">{title}</h3>
+    <ExternalLink className="Button Button--primary" href={buttonLink}>
+      {buttonText}
+    </ExternalLink>
+  </Box>
+);
+
+const Feature = ({ feature, included, expired, preview }) => (
+  <Box w={[1, 1 / 2, 1 / 4]} p={2}>
+    <Card
+      p={[1, 2]}
+      style={{
+        opacity: expired ? 0.5 : 1,
+        width: "100%",
+        height: 260,
+        backgroundColor: included ? undefined : colors["bg-light"],
+        color: included ? colors["text-dark"] : colors["text-medium"],
+      }}
+      className="relative flex flex-column layout-centered"
+    >
+      <StoreIcon
+        name={feature.icon}
+        color={
+          preview
+            ? colors["brand"]
+            : included
+            ? colors["success"]
+            : colors["text-medium"]
+        }
+      />
+
+      <Box my={2}>
+        <h3 className="text-dark">{feature.name}</h3>
+      </Box>
+
+      {preview ? (
+        <FeatureDescription feature={feature} />
+      ) : included ? (
+        <FeatureLinks
+          links={feature.docs}
+          defaultTitle={t`Learn how to use this`}
+        />
+      ) : (
+        <FeatureLinks links={feature.info} defaultTitle={t`Learn more`} />
+      )}
+
+      {!included && !preview && (
+        <div className="spread text-centered pt2 pointer-events-none">{t`Not included in your current plan`}</div>
+      )}
+    </Card>
+  </Box>
+);
+
+const FeatureDescription = ({ feature }) => (
+  <div className="text-centered">{feature.description}</div>
+);
+
+const FeatureLinks = ({ links, defaultTitle }) => (
+  <Flex align="center">
+    {links &&
+      links.map(({ link, title }) => (
+        <ExternalLink href={link} className="mx2 link">
+          {title || defaultTitle}
+        </ExternalLink>
+      ))}
+  </Flex>
+);
diff --git a/enterprise/frontend/src/metabase-enterprise/store/containers/StoreActivate.jsx b/enterprise/frontend/src/metabase-enterprise/store/containers/StoreActivate.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..468d8f7e40a91bc8ac8310b89dfc2990f14729a4
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/containers/StoreActivate.jsx
@@ -0,0 +1,97 @@
+import React from "react";
+import { Box, Flex } from "grid-styled";
+import { t } from "ttag";
+
+import colors from "metabase/lib/colors";
+
+import Button from "metabase/components/Button";
+import Link from "metabase/components/Link";
+
+import ModalWithTrigger from "metabase/components/ModalWithTrigger";
+
+import fitViewport from "metabase/hoc/FitViewPort";
+
+import { SettingsApi } from "metabase/services";
+
+@fitViewport
+export default class Activate extends React.Component {
+  state = {
+    heading: t`Enter the token you recieved from the store`,
+    errorMessage: "",
+    showVerbose: false,
+    error: false,
+  };
+  activate = async () => {
+    const value = this._input.value.trim();
+    if (!value) {
+      return false;
+    }
+    try {
+      await SettingsApi.put({ key: "premium-embedding-token", value });
+      // set window.location so we do a hard refresh
+      window.location = "/admin/store";
+    } catch (e) {
+      console.error(e.data);
+      this.setState({
+        error: true,
+        heading: e.data.message,
+        errorMessage: e.data["error-details"],
+      });
+    }
+  };
+  render() {
+    return (
+      <Flex
+        align="center"
+        justify="center"
+        className={this.props.fitClassNames}
+      >
+        <Flex align="center" flexDirection="column">
+          <Box my={3}>
+            <h2
+              className="text-centered"
+              style={{ color: this.state.error ? colors["error"] : "inherit" }}
+            >
+              {this.state.heading}
+            </h2>
+          </Box>
+          <Box>
+            <input
+              ref={ref => (this._input = ref)}
+              type="text"
+              className="input"
+              placeholder="XXXX-XXXX-XXXX-XXXX"
+            />
+            <Button ml={1} onClick={this.activate}>{t`Activate`}</Button>
+          </Box>
+
+          {this.state.error && (
+            <ModalWithTrigger
+              triggerElement={
+                <Box mt={3}>
+                  <Link
+                    className="link"
+                    onClick={() => this.setState({ showVerbose: true })}
+                  >{t`Need help?`}</Link>
+                </Box>
+              }
+              onClose={() => this.setState({ showVerbose: false })}
+              title={t`More info about your problem.`}
+              open={this.state.showVerbose}
+            >
+              <Box>{this.state.errorMessage}</Box>
+              <Flex my={2}>
+                <a
+                  className="ml-auto"
+                  href={`mailto:support@metabase.com?Subject="Issue with token activation for token ${this._input.value}"&Body="${this.state.errorMessage}"`}
+                >
+                  <Button primary>{t`Contact support`}</Button>
+                </a>
+              </Flex>
+            </ModalWithTrigger>
+          )}
+        </Flex>
+      </Flex>
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/store/index.js b/enterprise/frontend/src/metabase-enterprise/store/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b92ceff36f13c3ea5e07c5566ed8243f68b9931
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/index.js
@@ -0,0 +1,8 @@
+import { t } from "ttag";
+
+import { PLUGIN_ADMIN_NAV_ITEMS, PLUGIN_ADMIN_ROUTES } from "metabase/plugins";
+
+import getRoutes from "./routes";
+
+PLUGIN_ADMIN_NAV_ITEMS.push({ name: t`Enterprise`, path: "/admin/store" });
+PLUGIN_ADMIN_ROUTES.push(getRoutes);
diff --git a/enterprise/frontend/src/metabase-enterprise/store/lib/features.js b/enterprise/frontend/src/metabase-enterprise/store/lib/features.js
new file mode 100644
index 0000000000000000000000000000000000000000..811a43b45c223dd3aa43b6a360c345a76729639d
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/lib/features.js
@@ -0,0 +1,52 @@
+import { t } from "ttag";
+import MetabaseSettings from "metabase/lib/settings";
+
+const FEATURES = {
+  sandboxes: {
+    name: t`Data sandboxes`,
+    description: t`Make sure you're showing the right people the right data with automatic and secure filters based on user attributes.`,
+    icon: "lock",
+    docs: [
+      {
+        link: MetabaseSettings.docsUrl("enterprise-guide/data-sandboxes"),
+      },
+    ],
+  },
+  whitelabel: {
+    name: t`White labeling`,
+    description: t`Match Metabase to your brand with custom colors, your own logo and more.`,
+    icon: "star",
+    docs: [
+      {
+        link: MetabaseSettings.docsUrl("enterprise-guide/whitelabeling"),
+      },
+    ],
+  },
+  "audit-app": {
+    name: t`Auditing`,
+    description: t`Keep an eye on performance and behavior with robust auditing tools.`,
+    icon: "clipboard",
+    info: [{ link: "https://metabase.com/enterprise/" }],
+  },
+  sso: {
+    name: t`Single sign-on`,
+    description: t`Provide easy login that works with your exisiting authentication infrastructure.`,
+    icon: "group",
+    docs: [
+      {
+        title: "SAML",
+        link: MetabaseSettings.docsUrl(
+          "enterprise-guide/authenticating-with-saml",
+        ),
+      },
+      {
+        title: "JWT",
+        link: MetabaseSettings.docsUrl(
+          "enterprise-guide/authenticating-with-jwt",
+        ),
+      },
+    ],
+  },
+};
+
+export default FEATURES;
diff --git a/enterprise/frontend/src/metabase-enterprise/store/lib/services.js b/enterprise/frontend/src/metabase-enterprise/store/lib/services.js
new file mode 100644
index 0000000000000000000000000000000000000000..5492593f35bfda6a015c4764b5ce1dd95da1dbb7
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/lib/services.js
@@ -0,0 +1,5 @@
+import { GET } from "metabase/lib/api";
+
+export const StoreApi = {
+  tokenStatus: GET("/api/metastore/token/status"),
+};
diff --git a/enterprise/frontend/src/metabase-enterprise/store/routes.jsx b/enterprise/frontend/src/metabase-enterprise/store/routes.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8fab727172dbff7e37204dc3d1e327b11c147b37
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/store/routes.jsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { IndexRoute } from "react-router";
+import { t } from "ttag";
+
+import { Route } from "metabase/hoc/Title";
+
+import StoreActivate from "./containers/StoreActivate";
+import StoreAccount from "./containers/StoreAccount";
+
+export default function getRoutes() {
+  return (
+    <Route path="store" title={t`Store`}>
+      <IndexRoute component={StoreAccount} />
+      <Route path="activate" component={StoreActivate} />
+    </Route>
+  );
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/ColorSchemeWidget.jsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/ColorSchemeWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..92f1777bb4a86c98765fec7453dacc6e6714c2ad
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/ColorSchemeWidget.jsx
@@ -0,0 +1,99 @@
+import React from "react";
+
+import ColorPicker from "metabase/components/ColorPicker";
+import Icon from "metabase/components/Icon";
+import { humanize } from "metabase/lib/formatting";
+
+import { originalColors } from "../lib/whitelabel";
+
+const THEMEABLE_COLORS = [
+  "brand",
+  "nav",
+  ...Object.keys(originalColors).filter(name => name.startsWith("accent")),
+];
+
+const COLOR_DISPLAY_PROPERTIES = {
+  brand: {
+    name: "Primary color",
+    description:
+      "The main color used throughout the app for buttons, links, and the default chart color.",
+  },
+  nav: {
+    name: "Navigation bar color",
+    description:
+      "The top nav bar of Metabase. Defaults to the Primary Color if not set.",
+  },
+  accent1: {
+    name: "Accent 1",
+    description:
+      "The color of aggregations and breakouts in the graphical query builder.",
+  },
+  accent2: {
+    name: "Accent 2",
+    description:
+      "The color of filters in the query builder and buttons and links in filter widgets.",
+  },
+  accent3: {
+    name: "Additional chart color",
+  },
+  accent4: {
+    name: "Additional chart color",
+  },
+  accent5: {
+    name: "Additional chart color",
+  },
+  accent6: {
+    name: "Additional chart color",
+  },
+  accent7: {
+    name: "Additional chart color",
+  },
+};
+
+const ColorSchemeWidget = ({ setting, onChange }) => {
+  const value = setting.value || {};
+  const colors = { ...originalColors, ...value };
+
+  return (
+    <div>
+      <table>
+        <tbody>
+          {THEMEABLE_COLORS.map(name => {
+            const properties = COLOR_DISPLAY_PROPERTIES[name] || {};
+            return (
+              <tr>
+                <td>{properties.name || humanize(name)}:</td>
+                <td>
+                  <span className="mx1">
+                    <ColorPicker
+                      fancy
+                      triggerSize={16}
+                      value={colors[name]}
+                      onChange={color => onChange({ ...value, [name]: color })}
+                    />
+                  </span>
+                </td>
+                <td>
+                  {colors[name] !== originalColors[name] && (
+                    <Icon
+                      name="close"
+                      className="text-grey-2 text-grey-4-hover cursor-pointer"
+                      onClick={() => onChange({ ...value, [name]: undefined })}
+                    />
+                  )}
+                </td>
+                <td>
+                  <span className="mx2 text-grey-4">
+                    {properties.description}
+                  </span>
+                </td>
+              </tr>
+            );
+          })}
+        </tbody>
+      </table>
+    </div>
+  );
+};
+
+export default ColorSchemeWidget;
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoIcon.jsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoIcon.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b5301f3fc074ab18e4d74dfd4c22473770894f13
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoIcon.jsx
@@ -0,0 +1,133 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import cx from "classnames";
+
+import { removeAllChildren, parseDataUri } from "metabase/lib/dom";
+
+import { connect } from "react-redux";
+import { getLogoUrl } from "metabase-enterprise/settings/selectors";
+
+const mapStateToProps = state => ({
+  url: getLogoUrl(state),
+});
+
+@connect(mapStateToProps)
+export default class LogoIcon extends Component {
+  state = {
+    svg: null,
+  };
+
+  static defaultProps = {
+    height: 32,
+  };
+
+  static propTypes = {
+    size: PropTypes.number,
+    width: PropTypes.number,
+    height: PropTypes.number,
+    dark: PropTypes.bool,
+    className: PropTypes.string,
+    style: PropTypes.object,
+  };
+
+  componentDidMount() {
+    if (this.props.url) {
+      this.loadImage(this.props.url);
+    }
+  }
+
+  componentWillReceiveProps(newProps) {
+    if (newProps.url && newProps.url !== this.props.url) {
+      this.loadImage(newProps.url);
+    }
+  }
+
+  loadImage(url) {
+    if (this.xhr) {
+      this.xhr.abort();
+      this.xhr = null;
+    }
+
+    removeAllChildren(this._container);
+
+    const parsed = parseDataUri(url);
+    if (parsed) {
+      if (parsed.mimeType === "image/svg+xml") {
+        this._container.innerHTML = parsed.data;
+        const svg = this._container.getElementsByTagName("svg")[0];
+        if (svg) {
+          svg.setAttribute("fill", "currentcolor");
+          this.updateSize(svg);
+        } else {
+          this.loadImageFallback(url);
+        }
+      } else {
+        this.loadImageFallback(url);
+      }
+    } else {
+      const xhr = (this.xhr = new XMLHttpRequest());
+      xhr.open("GET", url);
+      xhr.onload = () => {
+        if (xhr.status < 200 || xhr.status >= 300) {
+          return;
+        }
+        const svg =
+          xhr.responseXML && xhr.responseXML.getElementsByTagName("svg")[0];
+        if (svg) {
+          svg.setAttribute("fill", "currentcolor");
+          this.updateSize(svg);
+
+          removeAllChildren(this._container);
+          this._container.appendChild(svg);
+        } else {
+          this.loadImageFallback(url);
+        }
+      };
+      xhr.onerror = () => {
+        this.loadImageFallback(url);
+      };
+      xhr.send();
+    }
+  }
+
+  loadImageFallback(url) {
+    removeAllChildren(this._container);
+
+    const img = document.createElement("img");
+    img.src = url;
+    this.updateSize(img);
+
+    this._container.appendChild(img);
+  }
+
+  updateSize(element) {
+    const width = this.props.width || this.props.size;
+    const height = this.props.height || this.props.size;
+    if (width) {
+      element.setAttribute("width", width);
+    } else {
+      element.removeAttribute("width");
+    }
+    if (height) {
+      element.setAttribute("height", height);
+    } else {
+      element.removeAttribute("height");
+    }
+  }
+
+  render() {
+    const { dark, style, className } = this.props;
+    return (
+      <span
+        ref={c => (this._container = c)}
+        className={cx(
+          "Icon",
+          { "text-brand": !dark },
+          { "text-white": dark },
+          className,
+        )}
+        style={style}
+      />
+    );
+  }
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9c1477248328663eddeea2acb684efd0e228d71f
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/LogoUpload.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+
+import Icon from "metabase/components/Icon";
+import LogoIcon from "metabase/components/LogoIcon";
+import SettingInput from "metabase/admin/settings/components/widgets/SettingInput.jsx";
+
+import { color } from "metabase/lib/colors";
+
+const LogoUpload = ({ setting, onChange, ...props }) => (
+  <div>
+    <div className="mb1">
+      {/* Preview of icon as it will appear in the nav bar */}
+      <span
+        className="mb1 p1 rounded flex layout-centered"
+        style={{ backgroundColor: color("nav") }}
+      >
+        <LogoIcon dark height={32} />
+      </span>
+    </div>
+    {window.File && window.FileReader ? (
+      <input
+        type="file"
+        onChange={e => {
+          if (e.target.files.length > 0) {
+            const reader = new FileReader();
+            reader.onload = e => onChange(e.target.result);
+            reader.readAsDataURL(e.target.files[0]);
+          }
+        }}
+      />
+    ) : (
+      <SettingInput setting={setting} onChange={onChange} {...props} />
+    )}
+    <Icon name="close" onClick={() => onChange(undefined)} />
+  </div>
+);
+
+export default LogoUpload;
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js b/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..716d620aed12c8bc6cb13b3c360c5200c389b063
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/index.js
@@ -0,0 +1,81 @@
+import {
+  PLUGIN_APP_INIT_FUCTIONS,
+  PLUGIN_LANDING_PAGE,
+  PLUGIN_LOGO_ICON_COMPONENTS,
+  PLUGIN_ADMIN_SETTINGS_UPDATES,
+  PLUGIN_SELECTORS,
+} from "metabase/plugins";
+
+import { t } from "ttag";
+
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import {
+  getIsWhitelabeled,
+  getHasCustomLogo,
+} from "metabase-enterprise/settings/selectors";
+import MetabaseSettings from "metabase/lib/settings";
+
+import ColorSchemeWidget from "./components/ColorSchemeWidget";
+import LogoUpload from "./components/LogoUpload";
+import LogoIcon from "./components/LogoIcon";
+import {
+  updateColors,
+  enabledApplicationNameReplacement,
+} from "./lib/whitelabel";
+
+if (hasPremiumFeature("whitelabel")) {
+  PLUGIN_LANDING_PAGE.push(() => MetabaseSettings.get("landing-page"));
+  PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => ({
+    whitelabel: {
+      name: "Whitelabel",
+      settings: [
+        {
+          key: "application-name",
+          display_name: t`Application Name`,
+          type: "string",
+        },
+        {
+          key: "application-colors",
+          display_name: t`Color Palette`,
+          widget: ColorSchemeWidget,
+        },
+        {
+          key: "application-logo-url",
+          display_name: t`Logo`,
+          type: "string",
+          widget: LogoUpload,
+        },
+        {
+          key: "application-favicon-url",
+          display_name: t`Favicon`,
+          type: "string",
+        },
+        {
+          key: "landing-page",
+          display_name: t`Landing Page`,
+          type: "string",
+          placeholder: "/",
+        },
+      ],
+    },
+    ...sections,
+  }));
+
+  PLUGIN_APP_INIT_FUCTIONS.push(({ root }) => {
+    MetabaseSettings.on("application-colors", updateColors);
+    MetabaseSettings.on("application-colors", () => {
+      root.forceUpdate();
+    });
+    updateColors();
+  });
+
+  enabledApplicationNameReplacement();
+
+  PLUGIN_LOGO_ICON_COMPONENTS.push(LogoIcon);
+}
+
+// these selectors control whitelabeling UI
+PLUGIN_SELECTORS.getShowAuthScene = (state, props) =>
+  !getIsWhitelabeled(state, props);
+PLUGIN_SELECTORS.getLogoBackgroundClass = (state, props) =>
+  getHasCustomLogo(state, props) ? "bg-brand" : "bg-white";
diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/whitelabel.js b/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/whitelabel.js
new file mode 100644
index 0000000000000000000000000000000000000000..66eff9931ef9f65c79d5c2db4127613453221ee3
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/lib/whitelabel.js
@@ -0,0 +1,196 @@
+import MetabaseSettings from "metabase/lib/settings";
+
+import Color from "color";
+
+import colors, { syncColors } from "metabase/lib/colors";
+import { addCSSRule } from "metabase/lib/dom";
+
+import memoize from "lodash.memoize";
+
+export const originalColors = { ...colors };
+
+const BRAND_NORMAL_COLOR = Color(colors.brand).hsl();
+const COLOR_REGEX = /(?:#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?\b|(?:rgb|hsl)a?\(\s*\d+\s*(?:,\s*\d+(?:\.\d+)?%?\s*){2,3}\))/;
+
+const CSS_COLOR_UPDATORS_BY_COLOR_NAME = {};
+const JS_COLOR_UPDATORS_BY_COLOR_NAME = {};
+
+// a color not found anywhere in the app
+const RANDOM_COLOR = Color({ r: 0xab, g: 0xcd, b: 0xed });
+
+function colorScheme() {
+  // FIXME: Ugh? initially load public setting as "application_color" but if the admin updates it
+  // we need to use "application-colors"
+  return (
+    MetabaseSettings.get("application-colors") ||
+    MetabaseSettings.get("application_colors")
+  );
+}
+
+function applicationName() {
+  // FIXME: Ugh? see comment in colorScheme()
+  return (
+    MetabaseSettings.get("application-name") ||
+    MetabaseSettings.get("application_name")
+  );
+}
+
+function walkStyleSheets(sheets, fn) {
+  for (const sheet of sheets) {
+    let rules = [];
+    try {
+      // try/catch due to CORS being enforced in Chrome
+      rules = sheet.cssRules || sheet.rules || [];
+    } catch (e) {}
+    for (const rule of rules) {
+      if (rule.cssRules) {
+        // child sheets, e.x. media queries
+        walkStyleSheets([rule], fn);
+      }
+      if (rule.style) {
+        for (const prop of rule.style) {
+          fn(rule.style, prop, rule.style[prop]);
+        }
+      }
+    }
+  }
+}
+
+const replaceColors = (cssValue, matchColor, replacementColor) => {
+  return cssValue.replace(COLOR_REGEX, colorString => {
+    const color = Color(colorString);
+    if (color.hex() === matchColor.hex()) {
+      if (color.alpha() < 1) {
+        return Color(replacementColor)
+          .alpha(color.alpha())
+          .string();
+      } else {
+        return replacementColor;
+      }
+    }
+    return colorString;
+  });
+};
+
+const getColorStyleProperties = memoize(function() {
+  const properties = [];
+  walkStyleSheets(document.styleSheets, (style, cssProperty, cssValue) => {
+    // don't bother with checking if there are no colors
+    if (COLOR_REGEX.test(cssValue)) {
+      properties.push({ style, cssProperty, cssValue });
+    }
+  });
+  return properties;
+});
+
+function initColorCSS(colorName) {
+  if (CSS_COLOR_UPDATORS_BY_COLOR_NAME[colorName]) {
+    return;
+  }
+  CSS_COLOR_UPDATORS_BY_COLOR_NAME[colorName] = [];
+
+  // special updator for brand
+  if (colorName === "brand") {
+    initCSSBrandHueUpdator();
+  }
+
+  const originalColor = Color(originalColors[colorName]);
+  // look for CSS rules which have colors matching the brand colors or very light or desaturated
+  for (const { style, cssProperty, cssValue } of getColorStyleProperties()) {
+    // try replacing with a random color to see if we actually need to
+    if (cssValue !== replaceColors(cssValue, originalColor, RANDOM_COLOR)) {
+      CSS_COLOR_UPDATORS_BY_COLOR_NAME[colorName].push(themeColor => {
+        style[cssProperty] = replaceColors(cssValue, originalColor, themeColor);
+      });
+    }
+  }
+}
+
+function initCSSBrandHueUpdator() {
+  // initialize the ".brand-hue" CSS rule, which is used to change the hue of images which should
+  // only contain the brand color or completely desaturated colors
+  const rotateHueRule = addCSSRule(".brand-hue", "filter: hue-rotate(0);");
+  CSS_COLOR_UPDATORS_BY_COLOR_NAME["brand"].push(themeColor => {
+    const degrees =
+      Color(themeColor)
+        .hsl()
+        .hue() - BRAND_NORMAL_COLOR.hue();
+    rotateHueRule.style["filter"] = `hue-rotate(${degrees}deg)`;
+  });
+}
+
+function initColorJS(colorName) {
+  if (JS_COLOR_UPDATORS_BY_COLOR_NAME[colorName]) {
+    return;
+  }
+  JS_COLOR_UPDATORS_BY_COLOR_NAME[colorName] = [];
+  JS_COLOR_UPDATORS_BY_COLOR_NAME[colorName].push(themeColor => {
+    colors[colorName] = themeColor;
+  });
+}
+
+function updateColorJS(colorName, themeColor) {
+  initColorJS(colorName);
+  for (const colorUpdator of JS_COLOR_UPDATORS_BY_COLOR_NAME[colorName]) {
+    colorUpdator(themeColor);
+  }
+  syncColors();
+}
+
+function updateColorCSS(colorName, themeColor) {
+  initColorCSS(colorName);
+  for (const colorUpdator of CSS_COLOR_UPDATORS_BY_COLOR_NAME[colorName]) {
+    colorUpdator(themeColor);
+  }
+}
+
+function updateColorsJS() {
+  const scheme = colorScheme();
+  for (const [colorName, themeColor] of Object.entries(scheme)) {
+    updateColorJS(colorName, themeColor);
+  }
+}
+
+function updateColorsCSS() {
+  const scheme = colorScheme();
+  for (const [colorName, themeColor] of Object.entries(scheme)) {
+    updateColorCSS(colorName, themeColor);
+  }
+}
+
+export function updateColors() {
+  updateColorsCSS();
+  updateColorsJS();
+}
+
+// APPLICATION NAME
+
+function replaceApplicationName(string) {
+  return string.replace(/Metabase/g, applicationName());
+}
+
+export function enabledApplicationNameReplacement() {
+  const c3po = require("ttag");
+  const _t = c3po.t;
+  const _jt = c3po.jt;
+  const _ngettext = c3po.ngettext;
+  c3po.t = (...args) => {
+    return replaceApplicationName(_t(...args));
+  };
+  c3po.ngettext = (...args) => {
+    return replaceApplicationName(_ngettext(...args));
+  };
+  c3po.jt = (...args) => {
+    return _jt(...args).map(element =>
+      typeof element === "string" ? replaceApplicationName(element) : element,
+    );
+  };
+}
+
+// Update the JS colors to ensure components that use a color statically get the
+// whitelabeled color (though this doesn't help if the admin changes a color and
+// doesn't refresh)
+// Don't update CSS colors yet since all the CSS hasn't been loaded yet
+try {
+  updateColorsJS();
+} catch (e) {}
diff --git a/enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg b/enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..89e18212d5a4d8addc8948a486c5aadef7c80ff1
Binary files /dev/null and b/enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg differ
diff --git a/enterprise/frontend/test/metabase-enterprise/audit/ad-hoc.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/audit/ad-hoc.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f971790025b11ba309616f33ee9d02bd703a043c
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/audit/ad-hoc.cy.spec.js
@@ -0,0 +1,44 @@
+import {
+  restore,
+  signInAsAdmin,
+  signInAsNormalUser,
+  describeWithToken,
+} from "__support__/cypress";
+
+describeWithToken("audit > ad-hoc", () => {
+  before(restore);
+  beforeEach(signInAsAdmin);
+
+  describe("native query with JOIN", () => {
+    before(() => {
+      cy.log("**Run ad hoc native query as normal user**");
+      signInAsNormalUser();
+
+      cy.visit("/question/new");
+      cy.findByText("Native query").click();
+      cy.get(".ace_content").type("SELECT 123");
+      cy.get(".Icon-play")
+        .first()
+        .click();
+
+      // make sure results rendered before moving forward
+      cy.get(".ScalarValue").contains("123");
+    });
+
+    it.skip("should appear in audit log (metabase-enterprise#486)", () => {
+      cy.visit("/admin/audit/members/log");
+
+      cy.findByText("Native")
+        .parent()
+        .within(() => {
+          cy.findByText("Ad-hoc").click();
+        });
+
+      cy.log("**Assert that the query page is not blank**");
+      cy.url().should("include", "/admin/audit/query/");
+
+      cy.get(".PageTitle").contains("Query");
+      cy.findByText("Open in Metabase");
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/audit/audit.e2e.spec.js b/enterprise/frontend/test/metabase-enterprise/audit/audit.e2e.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c46a0c26f8552ca76f8d863ccca1d3e0b7f725ca
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/audit/audit.e2e.spec.js
@@ -0,0 +1,43 @@
+import { t } from "ttag";
+import { createTestStore, useSharedAdminLogin } from "__support__/e2e";
+import { mount } from "enzyme";
+
+import { delay } from "metabase/lib/promise";
+
+import getAuditRoutes from "metabase-enterprise/audit_app/routes";
+import { PLUGIN_ADMIN_NAV_ITEMS, PLUGIN_ADMIN_ROUTES } from "metabase/plugins";
+
+describe("admin/audit", () => {
+  beforeAll(async () => {
+    useSharedAdminLogin();
+    // Ideally, we would mock the setting and then load metabase enterprise.
+    // However, here we directly alter the plugin instead. metabase-enterprise
+    // was probably already loaded, so altering the setting won't have an effect.
+    PLUGIN_ADMIN_NAV_ITEMS.push({ name: t`Audit`, path: "/admin/audit" });
+    PLUGIN_ADMIN_ROUTES.push(getAuditRoutes);
+  });
+
+  afterAll(() => {
+    // reset the plugins
+    PLUGIN_ADMIN_NAV_ITEMS.pop();
+    PLUGIN_ADMIN_ROUTES.pop();
+  });
+
+  it("should show the nav item and tab contents", async () => {
+    const store = await createTestStore();
+    store.pushPath("/admin/audit");
+    const app = mount(store.getAppContainer());
+
+    await delay(100);
+
+    // shows "Enterprise" in the top nav
+    expect(app.find(".NavItem").map(n => n.text())).toContain("Audit");
+
+    // displays the three expected tabs
+    expect(app.find(".Form-radio").map(n => n.parent().text())).toEqual([
+      "Overview",
+      "All members",
+      "Audit log",
+    ]);
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/audit/auditing.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/audit/auditing.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b94fd1c3bd4130574476c9f2fa6f5c2f827e26d
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/audit/auditing.cy.spec.js
@@ -0,0 +1,289 @@
+import {
+  restore,
+  signIn,
+  signOut,
+  signInAsAdmin,
+  USERS,
+  signInAsNormalUser,
+  withSampleDataset,
+  describeWithToken,
+} from "../../../../../frontend/test/__support__/cypress";
+
+const year = new Date().getFullYear();
+
+export function generateQuestions(users) {
+  users.forEach(user => {
+    signIn(user);
+
+    withSampleDataset(({ PRODUCTS }) => {
+      cy.request("POST", `/api/card`, {
+        name: `${user} test q`,
+        dataset_query: {
+          type: "native",
+          native: {
+            query: "SELECT * FROM products WHERE {{ID}}",
+            "template-tags": {
+              ID: {
+                id: "6b8b10ef-0104-1047-1e1b-2492d5954322",
+                name: "ID",
+                display_name: "ID",
+                type: "dimension",
+                dimension: ["field-id", PRODUCTS.ID],
+                "widget-type": "category",
+                default: null,
+              },
+            },
+          },
+          database: 1,
+        },
+        display: "scalar",
+        description: null,
+        visualization_settings: {},
+        collection_id: null,
+        result_metadata: null,
+        metadata_checksum: null,
+      });
+    });
+  });
+}
+export function generateDashboards(users) {
+  users.forEach(user => {
+    signIn(user);
+    cy.visit("/");
+    cy.get(".Icon-add").click();
+    cy.findByText("New dashboard").click();
+    cy.findByLabelText("Name").type(user + " test dash");
+    cy.get(".Icon-chevrondown").click();
+    cy.findAllByText("Our analytics")
+      .last()
+      .click();
+    cy.findByText("Create").click();
+  });
+}
+
+describeWithToken("audit > auditing", () => {
+  before(restore);
+  const users = ["admin", "normal"];
+
+  describe("Generate data to audit", () => {
+    beforeEach(signOut);
+
+    it("should create questions and dashboards", () => {
+      generateQuestions(users);
+      generateDashboards(users);
+    });
+
+    it("should view a dashboard", () => {
+      signIn("nodata");
+      cy.visit("/collection/root?type=dashboard");
+      cy.wait(3000)
+        .findByText(users[1] + " test dash")
+        .click();
+
+      cy.findByText("This dashboard is looking empty.");
+      cy.findByText("My personal collection").should("not.exist");
+    });
+
+    it("should view old question and new question", () => {
+      signIn("nodata");
+      cy.visit("/collection/root?type");
+      cy.wait(2000)
+        .findByText("Orders, Count")
+        .click();
+
+      cy.findByText("18,760");
+
+      cy.visit("/collection/root?type");
+      cy.wait(2000)
+        .findByText(users[0] + " test q")
+        .click();
+
+      cy.findByText("ID");
+    });
+
+    it("should download a question", () => {
+      signInAsNormalUser();
+      cy.visit("/question/3");
+      cy.server();
+      cy.get(".Icon-download").click();
+      cy.request("POST", "/api/card/1/query/json");
+    });
+  });
+
+  describe("See expected info on team member pages", () => {
+    beforeEach(signInAsAdmin);
+
+    const all_users = [
+      USERS.admin,
+      USERS.normal,
+      USERS.nodata,
+      USERS.nocollection,
+      USERS.none,
+    ];
+
+    it("should load the Overview tab", () => {
+      cy.visit("/admin/audit/members/overview");
+
+      cy.findByText("Active members and new members per day");
+      cy.findByText("No results!");
+      cy.wait(1000)
+        .get(".LineAreaBarChart")
+        .first()
+        .find("[width='0']")
+        .should("have.length", 2);
+      cy.get("svg")
+        .last()
+        .find("[width='0']")
+        .should("have.length", 3);
+    });
+
+    it("should load the All Members tab", () => {
+      cy.visit("/admin/audit/members/all");
+
+      all_users.forEach(user => {
+        cy.findByText(user.first_name + " " + user.last_name);
+      });
+      cy.get("tr")
+        .last()
+        .children()
+        .eq(-2)
+        .should("contain", year);
+    });
+
+    it.skip("should load the Audit log (Audit log should display views of dashboards)", () => {
+      cy.visit("/admin/audit/members/log");
+
+      cy.findAllByText("Orders, Count").should("have.length", 1);
+      cy.findAllByText("admin test q").should("have.length", 1);
+      cy.findAllByText("Sample Dataset").should("have.length", 4);
+      cy.findByText(users[1] + " test dash");
+    });
+  });
+
+  describe("See expected info on data pages", () => {
+    beforeEach(signInAsAdmin);
+
+    it("should load both tabs in Databases", () => {
+      // Overview tab
+      cy.visit("/admin/audit/databases/overview");
+      cy.findByText("Total queries and their average speed");
+      cy.findByText("No results!").should("not.exist");
+      cy.get(".LineAreaBarChart");
+      cy.get("rect");
+
+      // All databases tab
+      cy.visit("/admin/audit/databases/all");
+      cy.findByPlaceholderText("Database name");
+      cy.findByText("No results!").should("not.exist");
+      cy.findByText("Sample Dataset");
+      cy.findByText("Every hour");
+    });
+
+    it("should load both tabs in Schemas", () => {
+      // Overview tab
+      cy.visit("/admin/audit/schemas/overview");
+      cy.get("svg").should("have.length", 2);
+      cy.wait(1000).findAllByText("Sample Dataset PUBLIC");
+      cy.findAllByText("No results!").should("not.exist");
+
+      // All schemas tab
+      cy.visit("/admin/audit/schemas/all");
+      cy.findByText("PUBLIC");
+      cy.findByText("Saved Queries");
+    });
+
+    it("should load both tabs in Tables", () => {
+      // Overview tab
+      cy.visit("/admin/audit/tables/overview");
+      cy.findByText("Most-queried tables");
+      cy.findAllByText("No results!").should("not.exist");
+      cy.wait(1000).findAllByText("Sample Dataset PUBLIC ORDERS");
+
+      // *** Will fail when code below works again
+      cy.findAllByText("Sample Dataset PUBLIC PRODUCTS").should("not.exist");
+      // *** Products were there when creating qs by hand. Creating them by calling the api changes the result here.
+      // cy.wait(1000).findAllByText("Sample Dataset PUBLIC PRODUCTS");
+      // cy.get(".rowChart")
+      //   .first()
+      //   .find('[height="30"]')
+      //   .should("have.length", 2);
+      // cy.get(".rowChart")
+      //   .last()
+      //   .find("[height='30']")
+      //   .should("have.length", 2);
+
+      // All tables tab
+      cy.visit("/admin/audit/tables/all");
+      cy.findByPlaceholderText("Table name");
+      cy.findAllByText("PUBLIC").should("have.length", 4);
+      cy.findByText("REVIEWS");
+      cy.findByText("Reviews");
+    });
+  });
+
+  describe("See expected info on item pages", () => {
+    beforeEach(signInAsAdmin);
+
+    it("should load both tabs in Questions", () => {
+      // Overview tab
+      cy.visit("/admin/audit/questions/overview");
+      cy.findByText("Slowest queries");
+      cy.findByText("Query views and speed per day");
+      cy.findAllByText("No results!").should("not.exist");
+      cy.get(".LineAreaBarChart").should("have.length", 3);
+      cy.get("rect");
+      cy.get(".voronoi");
+
+      // All questions tab
+      cy.visit("/admin/audit/questions/all");
+      cy.findByPlaceholderText("Question name");
+      cy.wait(1000)
+        .findAllByText("Sample Dataset")
+        .should("have.length", 5);
+      cy.findByText("normal test q");
+      cy.findByText("Orders, Count, Grouped by Created At (year)");
+      cy.findByText("4").should("not.exist");
+    });
+
+    it("should load both tabs in Dashboards", () => {
+      // Overview tab
+      cy.visit("/admin/audit/dashboards/overview");
+      cy.findByText("Most popular dashboards and their avg loading times");
+      cy.findAllByText("Avg. Question Load Time (ms)");
+      cy.findByText("normal test dash");
+      cy.findByText("Orders");
+      cy.findByText("Orders, Count").should("not.exist");
+
+      // All dashboards tab
+      cy.visit("/admin/audit/dashboards/all");
+      cy.findByPlaceholderText("Dashboard name");
+      cy.findByText("admin test dash");
+      cy.findByText(USERS.normal.first_name + " " + USERS.normal.last_name);
+      cy.get("tr")
+        .eq(1)
+        .children()
+        .last()
+        .should("contain", year);
+    });
+
+    it("should load both tabs in Downloads", () => {
+      // Overview tab
+      cy.visit("/admin/audit/downloads/overview");
+      cy.findByText("No results!").should("not.exist");
+      cy.findByText("Largest downloads in the last 30 days");
+      cy.findByText(USERS.normal.first_name + " " + USERS.normal.last_name);
+
+      // All downloads tab
+      cy.visit("/admin/audit/downloads/all");
+      cy.wait(2000)
+        .findByText("No results")
+        .should("not.exist");
+      cy.get("tr")
+        .last()
+        .children()
+        .first()
+        .should("contain", year);
+      cy.findAllByText("GUI");
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/audit/containers/AuditTable.integ.spec.jsx b/enterprise/frontend/test/metabase-enterprise/audit/containers/AuditTable.integ.spec.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a400090481053ef5b0d9f89777a78a79a984d394
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/audit/containers/AuditTable.integ.spec.jsx
@@ -0,0 +1,61 @@
+import React from "react";
+import mock from "xhr-mock";
+import { mountWithStore } from "__support__/integration";
+
+import AuditTable from "metabase-enterprise/audit_app/containers/AuditTable";
+import { delay } from "metabase/lib/promise";
+
+const TABLE = {
+  card: {
+    name: "Query details",
+    display: "table",
+    dataset_query: {
+      type: "internal",
+      fn: "metabase-enterprise.audit.pages.query-detail/details",
+      args: ["asdf"],
+    },
+  },
+};
+
+describe("AuditTable", () => {
+  beforeEach(() => mock.setup());
+  afterEach(() => mock.teardown());
+
+  describe("pagination controls", () => {
+    beforeEach(() => {});
+    it("should not appear if there's not another page", async () => {
+      mock.post("/api/dataset", (req, res) =>
+        res.json({
+          data: { rows: [[0]], cols: [{ name: "x" }] },
+          row_count: 99,
+          status: "completed",
+        }),
+      );
+
+      const { wrapper } = mountWithStore(<AuditTable table={TABLE} />);
+
+      // ICK
+      await delay(100);
+      wrapper.update();
+
+      expect(wrapper.find(`[children="Rows 1-100"]`)).toHaveLength(0);
+    });
+    it("should appear if there's another page", async () => {
+      mock.post("/api/dataset", (req, res) =>
+        res.json({
+          data: { rows: [[0]], cols: [{ name: "x" }] },
+          row_count: 100,
+          status: "completed",
+        }),
+      );
+
+      const { wrapper } = mountWithStore(<AuditTable table={TABLE} />);
+
+      // ICK
+      await delay(100);
+      wrapper.update();
+
+      expect(wrapper.find(`[children="Rows 1-100"]`)).toHaveLength(1);
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/auth/LoginApp.unit.spec.js b/enterprise/frontend/test/metabase-enterprise/auth/LoginApp.unit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbb757d7d7e6426e31e9559c72327d7798b15d4e
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/auth/LoginApp.unit.spec.js
@@ -0,0 +1,29 @@
+import React from "react";
+
+import "__support__/mocks";
+import "metabase/plugins/builtin";
+import "metabase-enterprise/plugins";
+import LoginApp from "metabase/auth/containers/LoginApp";
+
+import { mountWithStore } from "__support__/integration";
+
+const SELECTOR_FOR_EMAIL_LINK = `[to="/auth/login/password"]`;
+
+jest.mock("metabase/components/LogoIcon", () => () => null);
+
+import Settings from "metabase/lib/settings";
+
+describe("LoginApp - Enterprise", () => {
+  describe("initial state", () => {
+    describe("with Google and password disabled", () => {
+      beforeEach(() => {
+        Settings.set("google-auth-client-id", 123);
+      });
+      it("should show the SSO button without an option to use password", () => {
+        const { wrapper } = mountWithStore(<LoginApp params={{}} />);
+        expect(wrapper.find("AuthProviderButton").length).toBe(1);
+        expect(wrapper.find(SELECTOR_FOR_EMAIL_LINK).length).toBe(0);
+      });
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/custom_drill_through/drill_through.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/custom_drill_through/drill_through.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ec27eb7dee91dc88f4b2353099783da5dc99eb0
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/custom_drill_through/drill_through.cy.spec.js
@@ -0,0 +1,85 @@
+import {
+  signIn,
+  signInAsAdmin,
+  restore,
+  modal,
+  describeWithToken,
+} from "__support__/cypress";
+
+describeWithToken("drill through", () => {
+  before(restore);
+
+  beforeEach(signInAsAdmin);
+  it("sets drill through link for dots in a line graph", () => {
+    cy.visit("/question/3");
+    cy.contains("Settings").click();
+    cy.contains("Drill-through")
+      .scrollIntoView()
+      .click();
+    cy.contains("Go to a custom link").click();
+    cy.contains("Link template")
+      .parent()
+      .find("input")
+      // We need to use a relative URL because otherwise it opens in a new tab
+      // and Cypress can't see it.
+      .type("/?count={{count}}", { parseSpecialCharSequences: false });
+    cy.contains("Done").click();
+
+    // save it, so it can be used in the next test
+    cy.contains("Save").click();
+    modal()
+      .find(".Button--primary")
+      .click();
+
+    // We need this force because the chart is animating. If we let Cypress wait
+    // for actionability, the dot will have been removed before it's clicked.
+    cy.get(".dot:first").click({ force: true });
+    cy.url().should("match", /\?count=744/);
+  });
+
+  it("should allow custom drill through without data permissions", () => {
+    signIn("nodata");
+    cy.visit("/question/3");
+    cy.get(".dot:first").click({ force: true });
+    cy.url().should("match", /\?count=744/);
+  });
+
+  xit("sets drill through link for value in table", () => {
+    cy.visit("/browse/1");
+    cy.contains("Orders").click();
+
+    // turn on link display for created at
+    cy.contains("Settings").click();
+    cy.contains("Visible columns")
+      .parent()
+      .contains("Created At")
+      .next()
+      .click();
+    cy.contains("Display as link")
+      .next()
+      .click();
+    cy.get(".PopoverContainer")
+      .contains("Link")
+      .click();
+
+    // enter templates
+    cy.contains("Link template")
+      .parent()
+      .find("input")
+      .type("http://example.com/?order={{id}}&product={{product_id}}", {
+        parseSpecialCharSequences: false,
+      });
+    cy.contains("Link text")
+      .parent()
+      .find("input")
+      .type("link-text-{{subtotal}}", { parseSpecialCharSequences: false });
+    cy.contains("Done").click();
+
+    // confirm templated values appear
+    cy.contains("link-text-110.93").should(
+      "have.attr",
+      "href",
+      "http://example.com/?order=2&product=123",
+    );
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/formatting/sandboxes.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/formatting/sandboxes.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..204d3455df9d2a54930e952e804730fbf4f53f3c
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/formatting/sandboxes.cy.spec.js
@@ -0,0 +1,388 @@
+import {
+  describeWithToken,
+  openOrdersTable,
+  popover,
+  restore,
+  signInAsAdmin,
+  signInAsNormalUser,
+  signOut,
+  withSampleDataset,
+} from "../../../../../frontend/test/__support__/cypress";
+
+const new_user = {
+  first_name: "Barb",
+  last_name: "Tabley",
+  username: "new@metabase.com",
+};
+
+// TODO: If we ever have the need to use this user across multiple tests, extract it to `__support__/cypress`
+const sandboxed_user = {
+  first_name: "User",
+  last_name: "1",
+  email: "u1@metabase.test",
+  password: "12341234",
+  login_attributes: {
+    user_id: "1",
+  },
+  // Because of the specific restrictions and the way testing dataset was set up,
+  // this user needs to also have access to "collections" (group_id: 4) in order to see saved questions
+  group_ids: [1, 4],
+};
+
+function createUser(user) {
+  return cy.request("POST", "/api/user", user);
+}
+
+describeWithToken("formatting > sandboxes", () => {
+  before(restore);
+
+  describe("Setup for sandbox tests", () => {
+    beforeEach(signInAsAdmin);
+
+    it("should make SQL question", () => {
+      withSampleDataset(({ PEOPLE }) => {
+        cy.request("POST", "/api/card", {
+          name: "sql param",
+          dataset_query: {
+            type: "native",
+            native: {
+              query: "select id,name,address,email from people where {{cid}}",
+              "template-tags": {
+                cid: {
+                  id: "6b8b10ef-0104-1047-1e1b-2492d5954555",
+                  name: "cid",
+                  "display-name": "CID",
+                  type: "dimension",
+                  dimension: ["field-id", PEOPLE.ID],
+                  "widget-type": "id",
+                },
+              },
+            },
+            database: 1,
+          },
+          display: "table",
+          visualization_settings: {},
+        });
+      });
+    });
+
+    it("should make a JOINs table", () => {
+      openOrdersTable();
+      cy.wait(1000)
+        .get(".Icon-notebook")
+        .click();
+      cy.wait(1000)
+        .findByText("Join data")
+        .click();
+      cy.findByText("Products").click();
+      cy.findByText("Visualize").click();
+      cy.findByText("Save").click();
+
+      cy.findByLabelText("Name")
+        .clear()
+        .wait(1)
+        .type("test joins table");
+      cy.findAllByText("Save")
+        .last()
+        .click();
+      cy.findByText("Not now").click();
+    });
+  });
+
+  describe("Sandboxes should work", () => {
+    beforeEach(signInAsNormalUser);
+
+    it("should add key attributes to new user and existing user", () => {
+      signOut();
+      signInAsAdmin();
+
+      // Existing user
+      cy.visit("/admin/people");
+      cy.get(".Icon-ellipsis")
+        .last()
+        .click();
+      cy.findByText("Edit user").click();
+      cy.findByText("Add an attribute").click();
+      cy.findByPlaceholderText("Key").type("User ID");
+      cy.findByPlaceholderText("Value").type("3");
+      cy.findByText("Update").click();
+
+      // New user
+      cy.visit("/admin/people");
+      cy.findByText("Add someone").click();
+      cy.findByPlaceholderText("Johnny").type(new_user.first_name);
+      cy.findByPlaceholderText("Appleseed").type(new_user.last_name);
+      cy.findByPlaceholderText("youlooknicetoday@email.com").type(
+        new_user.username,
+      );
+      cy.findByText("Add an attribute").click();
+      cy.findByPlaceholderText("Key").type("User ID");
+      cy.findByPlaceholderText("Value").type("1");
+      cy.findAllByText("Create").click();
+      cy.findByText("Done").click();
+    });
+
+    it("should change sandbox permissions as admin", () => {
+      signOut();
+      signInAsAdmin();
+      const ADMIN_GROUP = 2;
+      const DATA_GROUP = 5;
+
+      // Changes Orders permssions to use filter and People to use SQL filter
+      withSampleDataset(
+        ({ ORDERS_ID, PEOPLE_ID, PRODUCTS_ID, REVIEWS_ID, ORDERS }) => {
+          cy.request("POST", "/api/mt/gtap", {
+            id: 1,
+            group_id: DATA_GROUP,
+            table_id: ORDERS_ID,
+            card_id: null,
+            attribute_remappings: {
+              "User ID": ["dimension", ["field-id", ORDERS.USER_ID]],
+            },
+          });
+          cy.request("POST", "/api/mt/gtap", {
+            group_id: DATA_GROUP,
+            table_id: PEOPLE_ID,
+            card_id: 4,
+            attribute_remappings: {
+              "User ID": ["dimension", ["template-tag", "cid"]],
+            },
+          });
+          cy.request("PUT", "/api/permissions/graph", {
+            revision: 1,
+            groups: {
+              [ADMIN_GROUP]: { "1": { native: "write", schemas: "all" } },
+              [DATA_GROUP]: {
+                "1": {
+                  schemas: {
+                    PUBLIC: {
+                      [ORDERS_ID]: { query: "segmented", read: "all" },
+                      [PEOPLE_ID]: { query: "segmented", read: "all" },
+                      [PRODUCTS_ID]: "all",
+                      [REVIEWS_ID]: "all",
+                    },
+                  },
+                },
+              },
+            },
+          });
+        },
+      );
+    });
+
+    it("should be sandboxed with a filter (on normal table)", () => {
+      cy.visit("/browse/1");
+      cy.findByText("Orders").click();
+
+      // Table filter - only 10 rows should show up
+      cy.contains("Showing 10");
+
+      // And those rows should only show the User ID of 3
+      // First get the number of columns...
+      // And then find the index of the column that contains "User ID"
+      // Then ensure every nth element of that column only contains the desired User ID
+      // TODO: If we use this again, it should go in a helper
+      cy.get(".TableInteractive-headerCellData")
+        .its("length")
+        .then(columnCount => {
+          cy.contains(".TableInteractive-headerCellData", "User ID")
+            .invoke("index")
+            .then(userIDIndex => {
+              cy.get(".cellData")
+                .its("length")
+                .then(cellCountWithHeaders => {
+                  const range = (start, stop, step) =>
+                    Array.from(
+                      { length: (stop - start) / step + 1 },
+                      (_, i) => start + i * step,
+                    );
+                  // Loop over the columns starting at the zero-indexed second row (first row is headers)
+                  // userIDIndex is already zero-indexed, so we just add that to the number of columns
+                  const genArr = range(
+                    columnCount + userIDIndex,
+                    cellCountWithHeaders,
+                    columnCount,
+                  );
+                  cy.wrap(genArr).each(index => {
+                    cy.get(".cellData")
+                      .eq(index)
+                      .should("have.text", "3");
+                  });
+                });
+            });
+        });
+
+      // Notebook filter
+      cy.get(".Icon-notebook").click();
+      cy.findByText("Summarize").click();
+      cy.findByText("Count of rows").click();
+      cy.findByText("Visualize").click();
+      cy.get(".ScalarValue");
+      cy.findByText("18,760").should("not.exist");
+      cy.findByText("10");
+    });
+
+    it("should be sandboxed with a filter (on a saved JOINed question)", () => {
+      cy.visit("/question/5");
+
+      cy.wait(2000)
+        .get(".TableInteractive-cellWrapper--firstColumn")
+        .should("have.length", 11);
+    });
+
+    it("should be sandbox with a filter (after applying a filter to a JOINed question)", () => {
+      cy.visit("/question/5");
+
+      // Notebook filter
+      cy.get(".Icon-notebook").click();
+      cy.wait(2000)
+        .findByText("Filter")
+        .click();
+      cy.findAllByText("Total")
+        .last()
+        .click();
+      cy.findByText("Equal to").click();
+      cy.findByText("Greater than").click();
+      cy.findByPlaceholderText("Enter a number").type("100");
+      cy.findByText("Add filter").click();
+      cy.findByText("Visualize").click();
+      cy.wait(2000)
+        .get(".TableInteractive-cellWrapper--firstColumn")
+        .should("have.length", 7);
+    });
+
+    it("should filter categories on saved SQL question (for a new question - column number)", () => {
+      cy.visit("/question/new?database=1&table=3");
+      cy.get(".TableInteractive-cellWrapper--firstColumn").should(
+        "have.length",
+        2,
+      );
+    });
+
+    it("should filter categories on saved SQL question (for a new question - row number)", () => {
+      cy.visit("/question/new?database=1&table=3");
+      cy.get(".TableInteractive-headerCellData").should("have.length", 4);
+    });
+  });
+
+  describe("Sandboxed drill-through", () => {
+    before(() => {
+      restore();
+      signInAsAdmin();
+      createUser(sandboxed_user);
+    });
+
+    it.skip("Should allow drill-through for sandboxed user (metabase-enterprise#535)", () => {
+      cy.visit("/");
+
+      cy.get(".Icon-gear")
+        .first()
+        .click();
+      cy.findByText("Admin").click();
+      cy.findByText("Permissions").click();
+      cy.findByText("View tables").click();
+
+      /**
+       * Give sandboxed access for Orders (first x)
+       * 
+       |          | All users | collection |
+       |----------|-----------|------------|
+       | Orders   |     x     |      x     |
+       | People   |     x     |      x     |
+       | Products |     x     |      x     |
+       | Reviews  |     x     |      x     |
+       */
+      cy.get(".Icon-close")
+        .eq(0)
+        .click();
+      cy.findByText("Grant sandboxed access").click();
+      cy.findByText("Change").click();
+      cy.findByText("Pick a column").click();
+      cy.findByText("User ID").click();
+      cy.findByText("Pick a user attribute").click();
+      cy.findByText("user_id").click();
+      cy.findByText("Save").click();
+
+      /**
+       * Give unrestricted access for Products (fourth x)
+       * 
+       |          | All users | collection |
+       |----------|-----------|------------|
+       | Orders   |     s     |      x     |
+       | People   |     x     |      x     |
+       | Products |     x     |      x     |
+       | Reviews  |     x     |      x     |
+       */
+      cy.get(".Icon-close")
+        .eq(3)
+        .click();
+      cy.findByText("Grant unrestricted access").click();
+      cy.findByText("Save Changes").click();
+
+      // Save all changes to permissions
+      cy.get(".Modal").within(() => {
+        cy.findByText("Save permissions?");
+        cy.findByText("Yes").click();
+      });
+
+      // Go straight to orders table in custom questions
+      cy.visit("/question/new?database=1&table=2&mode=notebook");
+
+      // Orders join Products, Count by Category
+      cy.get(".Icon-join_left_outer").click();
+      popover().within(() => cy.findByText("Products").click());
+      cy.findByText("Summarize").click();
+      popover().within(() => cy.findByText("Count of rows").click());
+      cy.findByText("Pick a column to group by").click();
+      popover().within(() => {
+        cy.findByText("Product").click();
+        cy.findByText("Category").click();
+      });
+
+      const questionTitle = "Question 1";
+      // Save question,
+      cy.findByText("Save").click();
+      cy.findByLabelText("Name")
+        .clear() // clear pre-populated name,
+        .type(questionTitle);
+      cy.get(".Modal").within(() => {
+        cy.findByText("Save").click();
+      });
+      // and don't save it to a dashboard
+      cy.findByText("Not now").click();
+
+      signOut();
+
+      // Sign in as newly created sandboxed_user ("User 1")
+      cy.visit("/");
+      cy.findByLabelText("Email address").type(sandboxed_user.email);
+      cy.findByLabelText("Password").type(sandboxed_user.password);
+      cy.findByText("Sign in").click();
+
+      // Find saved question in "Our analytics"
+      cy.findByText("Browse all items").click();
+      cy.findByText(questionTitle).click();
+      // Close the first-time popup saying: "It is ok to play with saved questions"
+      cy.findByText("Okay").click();
+      // The question is originally displayed as table
+      // Set its visualization/graph to "Bar"
+      cy.findByText("Visualization").click();
+      cy.get(".Icon-bar").click();
+      cy.findByText("Done").click();
+      cy.get(".Visualization").within(() => {
+        // and click on any of the 4 bars in the graph
+        cy.get(".bar")
+          .eq(0) // there is no special reason we chose the first one
+          .click({ force: true });
+      });
+      cy.server();
+      cy.route("POST", "/api/dataset").as("view-dataset");
+      popover().within(() => cy.findByText("View these Orders").click());
+      cy.wait("@view-dataset");
+
+      cy.findByText("There was a problem with your question").should(
+        "not.exist",
+      );
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/formatting/whitelabel.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/formatting/whitelabel.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..68bef57adafb7a28e73fb27ef0d4da178febacae
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/formatting/whitelabel.cy.spec.js
@@ -0,0 +1,245 @@
+import {
+  restore,
+  signInAsAdmin,
+  signOut,
+  signInAsNormalUser,
+  openOrdersTable,
+  describeWithToken,
+} from "../../../../../frontend/test/__support__/cypress";
+
+const main_color = {
+  // brown - button, links, chart
+  hex: "8B572A",
+  rgb: "(139, 87, 42)",
+};
+
+//green - nav bar
+const header_color = "rgb(40, 78, 7)";
+
+function changeThemeColor(location, colorhex) {
+  cy.get("td")
+    .eq(location)
+    .click();
+  cy.get(`div[title='#${colorhex}']`).click();
+  cy.findByText("Done").click();
+}
+function checkFavicon() {
+  cy.request("/api/setting/application-favicon-url")
+    .its("body")
+    .should("include", "https://cdn.ecosia.org/assets/images/ico/favicon.ico");
+}
+function checkLogo() {
+  cy.readFile(
+    "enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg",
+    "base64",
+  ).then(logo_data => {
+    cy.get(`img[src="data:image/jpeg;base64,${logo_data}"]`);
+  });
+}
+
+describeWithToken("formatting > whitelabel", () => {
+  before(restore);
+
+  describe("Changes to company name work", () => {
+    beforeEach(signOut);
+
+    it("should change company name", () => {
+      signInAsAdmin();
+      cy.visit("/admin/settings/whitelabel");
+      cy.findByPlaceholderText("Metabase")
+        .clear()
+        .type("Test Co");
+      // *** In html, is not text, only value
+      cy.findByText("Application Name").click();
+
+      cy.findByText("Saved");
+      cy.get("input").should("have.value", "Test Co");
+    });
+
+    it("should show new name on activity page as admin", () => {
+      signInAsAdmin();
+      cy.visit("/activity");
+      cy.findByText("Test Co is up and running.");
+      cy.findByText("Metabase is up and running.").should("not.exist");
+    });
+
+    it("should show new name when logged out", () => {
+      cy.visit("/");
+      cy.wait(2000).findByText("Sign in to Test Co");
+    });
+
+    it("should show new name on activity page as user", () => {
+      signInAsNormalUser();
+      cy.visit("/activity");
+      cy.findByText("Test Co is up and running.");
+      cy.findByText("Metabase is up and running.").should("not.exist");
+    });
+  });
+
+  describe("Changes to theme colors work", () => {
+    it("should change theme colors in admin panel", () => {
+      signInAsAdmin();
+      cy.visit("/admin/settings/whitelabel");
+
+      // Select color with squares
+      changeThemeColor(1, main_color.hex);
+
+      // Select color by entering rgb
+      cy.get("td")
+        .eq(5)
+        .click();
+      cy.get(".sketch-picker")
+        .find("input")
+        .eq(1)
+        .clear()
+        .type("40");
+      cy.get(".sketch-picker")
+        .find("input")
+        .eq(2)
+        .clear()
+        .type("78");
+      cy.get(".sketch-picker")
+        .find("input")
+        .eq(3)
+        .clear()
+        .type("7");
+      cy.findByText("Done").click();
+
+      // Select colors with squares
+      changeThemeColor(9, "417505");
+      changeThemeColor(13, "7ED321");
+      changeThemeColor(17, "B8E986");
+      changeThemeColor(21, "50E3C2");
+      changeThemeColor(25, "4A90E2");
+
+      // Select color by typing hex code
+      cy.get("td")
+        .eq(29)
+        .click();
+      cy.get(".sketch-picker")
+        .find("input")
+        .first()
+        .clear()
+        .type("082CBE");
+      cy.findByText("Done").click();
+
+      changeThemeColor(33, "F8E71C");
+
+      cy.get(".Icon-close").should("have.length", 10);
+    });
+
+    it("should show color changes on admin's dashboard", () => {
+      signInAsAdmin();
+      cy.visit("/");
+      cy.get(`[style='background-color: ${header_color};']`);
+    });
+
+    it("should show color changes when signed out", () => {
+      signOut();
+      cy.visit("/");
+      cy.get(
+        `[style='width: 16px; height: 16px; background-color: rgb${main_color.rgb}; border: 2px solid rgb${main_color.rgb};']`,
+      );
+    });
+
+    it("should show color changes on user's dashboard", () => {
+      signInAsNormalUser();
+      cy.visit("/");
+      cy.get(`[style='background-color: ${header_color};']`);
+    });
+
+    it.skip("should show color changes reflected in q visualizations (metabase-enterprise #470)", () => {
+      // *** Test should pass when issue #470 is resolved
+      signInAsNormalUser();
+      openOrdersTable();
+      cy.wait(3000)
+        .findAllByText("Summarize")
+        .first()
+        .click();
+      cy.wait(1000)
+        .findByText("Price")
+        .click();
+      cy.findByText("Done").click();
+
+      cy.get(`div[fill='#${main_color.hex};']`);
+      cy.get(`rect[fill='#509EE3']`).should("not.exist");
+    });
+  });
+
+  describe("Changes to logo work", () => {
+    it("should add a logo", () => {
+      signInAsAdmin();
+      cy.visit("/admin/settings/whitelabel");
+
+      cy.server();
+      cy.readFile(
+        "enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg",
+        "base64",
+      ).then(logo_data => {
+        cy.request("PUT", "/api/setting/application-logo-url", {
+          placeholder:
+            "enterprise/frontend/test/metabase-enterprise/_support_/logo.jpeg",
+          default: "app/assets/img/logo.svg",
+          description:
+            "For best results, use an SVG file with a transparent background.",
+          display_name: "Logo",
+          env_name: "MB_APPLICATION_LOGO_URL",
+          is_env_setting: false,
+          type: "string",
+          value: `data:image/jpeg;base64,${logo_data}`,
+          originalValue: null,
+        });
+      });
+    });
+
+    it("should reflect logo change on admin's dashboard", () => {
+      signInAsAdmin();
+      cy.visit("/");
+      checkLogo();
+    });
+
+    it("should reflect logo change while signed out", () => {
+      cy.visit("/");
+      checkLogo();
+    });
+
+    it("should reflect logo change on user's dashboard", () => {
+      signInAsNormalUser();
+      cy.visit("/");
+      checkLogo();
+    });
+  });
+
+  describe("Changes to favicon work", () => {
+    it("should add a favicon", () => {
+      signInAsAdmin();
+      cy.visit("/admin/settings/whitelabel");
+
+      cy.server();
+
+      cy.findByPlaceholderText("frontend_client/favicon.ico").type(
+        "https://cdn.ecosia.org/assets/images/ico/favicon.ico",
+      );
+      cy.get("ul")
+        .eq(2)
+        .click("right");
+      cy.wait(10).findByText("Saved");
+
+      checkFavicon();
+    });
+
+    it("should reflect favicon change in API", () => {
+      signInAsAdmin();
+      cy.visit("/");
+      checkFavicon();
+    });
+
+    it("should reflect favicon change in HTML", () => {
+      signInAsNormalUser();
+      cy.visit("/");
+      cy.get('head link[rel="icon"]')
+        .get('[href="https://cdn.ecosia.org/assets/images/ico/favicon.ico"]')
+        .should("have.length", 1);
+    });
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/snippets/snippet-permissions.cy.spec.js b/enterprise/frontend/test/metabase-enterprise/snippets/snippet-permissions.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..15c488a5439ef130ed9bf4bc69b731fa8d8b56ac
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/snippets/snippet-permissions.cy.spec.js
@@ -0,0 +1,81 @@
+import {
+  signInAsNormalUser,
+  signInAsAdmin,
+  restore,
+  modal,
+  popover,
+  sidebar,
+  describeWithToken,
+} from "__support__/cypress";
+
+describeWithToken("scenarios > question > snippets", () => {
+  before(restore);
+  beforeEach(signInAsNormalUser);
+
+  it("should let you create a snippet folder and move a snippet into it", () => {
+    cy.visit("/question/new");
+    cy.contains("Native query").click();
+
+    // create snippet via API
+    cy.request("POST", "/api/native-query-snippet", {
+      content: "snippet 1",
+      name: "snippet 1",
+      collection_id: null,
+    });
+
+    // create folder
+    cy.get(".Icon-snippet").click();
+    sidebar()
+      .find(".Icon-add")
+      .click();
+    popover().within(() => cy.findByText("New folder").click());
+    modal().within(() => {
+      cy.findByText("Create your new folder");
+      cy.findByLabelText("Give your folder a name").type(
+        "my favorite snippets",
+      );
+      cy.findByText("Create").click();
+    });
+
+    // move snippet into folder
+    sidebar()
+      .findByText("snippet 1")
+      .parent()
+      .parent()
+      .parent()
+      .within(() => {
+        cy.get(".Icon-chevrondown").click({ force: true });
+        cy.findByText("Edit").click();
+      });
+    modal().within(() => cy.findByText("Top folder").click());
+    popover().within(() => cy.findByText("my favorite snippets").click());
+    cy.server();
+    cy.route("/api/collection/root/items?namespace=snippets").as("updateList");
+    modal().within(() => cy.findByText("Save").click());
+
+    // check that everything is in the right spot
+    cy.wait("@updateList");
+    cy.queryByText("snippet 1").should("not.exist");
+    cy.findByText("my favorite snippets").click();
+    cy.findByText("snippet 1");
+  });
+
+  it("should allow updating snippet folder permissions", () => {
+    signInAsAdmin();
+    cy.visit("/question/new");
+    cy.contains("Native query").click();
+    cy.get(".Icon-snippet").click();
+
+    sidebar()
+      .findByText("my favorite snippets")
+      .parent()
+      .parent()
+      .find(".Icon-ellipsis")
+      .click({ force: true });
+    popover().within(() => cy.findByText("Change permissions").click());
+    modal().within(() => {
+      cy.findByText("Permissions for this folder");
+    });
+    // TODO: incomplete
+  });
+});
diff --git a/enterprise/frontend/test/metabase-enterprise/store/store.e2e.spec.js b/enterprise/frontend/test/metabase-enterprise/store/store.e2e.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f1985731bd4fb4b83087003e4be25c8938b2fbb
--- /dev/null
+++ b/enterprise/frontend/test/metabase-enterprise/store/store.e2e.spec.js
@@ -0,0 +1,30 @@
+import { createTestStore, useSharedAdminLogin } from "__support__/e2e";
+import { mount } from "enzyme";
+
+import "metabase/plugins/builtin";
+import "metabase-enterprise/plugins";
+
+describe("admin/store", () => {
+  beforeAll(async () => {
+    useSharedAdminLogin();
+  });
+
+  it("should show the nav item and tab contents", async () => {
+    const store = await createTestStore();
+    store.pushPath("/admin/store");
+    const app = mount(store.getAppContainer());
+
+    // shows "Enterprise" in the top nav
+    const navItems = await app.async.find(".NavItem");
+    expect(navItems.map(n => n.text())).toContain("Enterprise");
+
+    // displays the four feature boxes
+    const headings = await app.async.find("h3");
+    expect(headings.map(n => n.text())).toEqual([
+      "Data sandboxes",
+      "White labeling",
+      "Auditing",
+      "Single sign-on",
+    ]);
+  });
+});
diff --git a/frontend/interfaces/global.js b/frontend/interfaces/global.js
old mode 100644
new mode 100755
diff --git a/frontend/interfaces/grid-styled.js b/frontend/interfaces/grid-styled.js
old mode 100644
new mode 100755
diff --git a/frontend/interfaces/icepick.js b/frontend/interfaces/icepick.js
old mode 100644
new mode 100755
diff --git a/frontend/interfaces/redux-actions_v2.x.x.js b/frontend/interfaces/redux-actions_v2.x.x.js
old mode 100644
new mode 100755
diff --git a/frontend/lint/eslint-rules/no-color-literals.js b/frontend/lint/eslint-rules/no-color-literals.js
old mode 100644
new mode 100755
diff --git a/frontend/src/metabase/.eslintrc b/frontend/src/metabase/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..b5d6afb7bfa91b22d5d9858956ddf06d088ec7d8
--- /dev/null
+++ b/frontend/src/metabase/.eslintrc
@@ -0,0 +1,8 @@
+{
+  "rules": {
+    "no-restricted-imports": [
+      "error",
+      { "patterns": ["metabase-enterprise", "metabase-enterprise/*"] }
+    ]
+  }
+}
diff --git a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
index eb93593c6cd9b7dce81092e27e76c6bb03c15aea..b3d6269896ad3064662c7e7405d78f0e1442450d 100644
--- a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx
@@ -322,6 +322,7 @@ const FieldGeneralPane = ({
         description={t`When this field is used in a filter, what should people use to enter the value they want to filter on?`}
       />
       <Select
+        className="inline-block"
         value={field.has_field_values}
         onChange={({ target: { value } }) =>
           onUpdateFieldProperties({
diff --git a/frontend/src/metabase/app.js b/frontend/src/metabase/app.js
index fe350a6120d1eb300a31984710b42ae401386017..0a351c36c50fb3c8b71f889993968a3100f8ff79 100644
--- a/frontend/src/metabase/app.js
+++ b/frontend/src/metabase/app.js
@@ -22,6 +22,11 @@ import "metabase/lib/colors";
 // NOTE: this loads all builtin plugins
 import "metabase/plugins/builtin";
 
+// This is conditionally aliased in the webpack config.
+// If EE isn't enabled, it loads an empty file.
+// $FlowFixMe
+import "ee-plugins"; // eslint-disable-line import/no-unresolved
+
 import { PLUGIN_APP_INIT_FUCTIONS } from "metabase/plugins";
 
 import registerVisualizations from "metabase/visualizations/register";
diff --git a/frontend/src/metabase/components/FieldValuesWidget.jsx b/frontend/src/metabase/components/FieldValuesWidget.jsx
index 73d004b1304a60871f02c2f21c798d047ded6736..d5ea1cbab60b4a10bf88b2f636ffdc4d65f352e2 100644
--- a/frontend/src/metabase/components/FieldValuesWidget.jsx
+++ b/frontend/src/metabase/components/FieldValuesWidget.jsx
@@ -178,11 +178,13 @@ export class FieldValuesWidget extends Component {
   }
 
   hasList() {
+    const nonEmptyArray = a => a && a.length > 0;
     return (
       this.shouldList() &&
       (this.useChainFilterEndpoints()
-        ? this.state.loadingState === "LOADED"
-        : this.props.fields.every(field => field.values))
+        ? this.state.loadingState === "LOADED" &&
+          nonEmptyArray(this.state.options)
+        : this.props.fields.every(field => nonEmptyArray(field.values)))
     );
   }
 
diff --git a/frontend/src/metabase/components/Popover.css b/frontend/src/metabase/components/Popover.css
index 413c8a71a71054703610186c3f6582c42b4dac40..8c2af4e6910ec14b3f1e2a5a8d77ea37ecf1b64d 100644
--- a/frontend/src/metabase/components/Popover.css
+++ b/frontend/src/metabase/components/Popover.css
@@ -40,7 +40,6 @@
   line-height: 1.26;
   padding: 10px 12px;
 }
-
 .PopoverBody.PopoverBody--tooltip.PopoverBody--tooltipConstrainedWidth {
   font-size: 12px;
   max-width: 200px;
diff --git a/frontend/src/metabase/components/Select.jsx b/frontend/src/metabase/components/Select.jsx
index 35f8031bc41840bae9c06c99c9b84a18e2d48d2d..787e708981a50feec8788934454a0d00ce770cf7 100644
--- a/frontend/src/metabase/components/Select.jsx
+++ b/frontend/src/metabase/components/Select.jsx
@@ -70,7 +70,8 @@ export default class Select extends Component {
     const _getValue = props =>
       // If a defaultValue is passed, replace a null value with it.
       // Otherwise, allow null values since we sometimes want them.
-      props.hasOwnProperty("defaultValue") && props.value == null
+      Object.prototype.hasOwnProperty.call(props, "defaultValue") &&
+      props.value == null
         ? props.defaultValue
         : props.value;
 
diff --git a/frontend/src/metabase/css/core/colors.css b/frontend/src/metabase/css/core/colors.css
index 7a5880420f0cce094774e6b818b5f682a2a7881f..c59a46fd4e07ae5006d8a3c73ed733df179bcf4b 100644
--- a/frontend/src/metabase/css/core/colors.css
+++ b/frontend/src/metabase/css/core/colors.css
@@ -3,6 +3,7 @@
  */
 :root {
   --color-brand: #509ee3;
+  --color-brand-light: #ddecfa;
   --color-accent1: #88bf4d;
   --color-accent2: #a989c5;
   --color-accent3: #ef8c8c;
@@ -110,7 +111,21 @@
 :local(.text-brand-light),
 .text-brand-light-hover:hover,
 :local(.text-brand-light-hover):hover {
-  color: var(--color-text-light);
+  color: var(--color-brand-light);
+}
+
+.bg-brand-light {
+  background-color: var(--color-brand-light);
+}
+
+.bg-brand-dark,
+.bg-brand-dark-hover:hover {
+  background-color: color(var(--color-brand) blackness(+12%));
+}
+
+.bg-brand-dark,
+.bg-brand-dark-hover:hover {
+  background-color: color(var(--color-brand) blackness(+12%));
 }
 
 .bg-brand-dark,
diff --git a/frontend/src/metabase/lib/colors.js b/frontend/src/metabase/lib/colors.js
index 46eab259c4540cfd422d73323cf75a62c8d0ae89..596553802510b5dc9e2561ac8dd6fa90c63620f4 100644
--- a/frontend/src/metabase/lib/colors.js
+++ b/frontend/src/metabase/lib/colors.js
@@ -15,6 +15,7 @@ export type ColorFamily = { [name: ColorName]: ColorString };
 /* eslint-disable no-color-literals */
 const colors = {
   brand: "#509EE3",
+  "brand-light": "#DDECFA",
   accent1: "#88BF4D",
   accent2: "#A989C5",
   accent3: "#EF8C8C",
@@ -234,7 +235,7 @@ const PREFERRED_COLORS = {
 
 const PREFERRED_COLORS_MAP = {};
 for (const color in PREFERRED_COLORS) {
-  if (PREFERRED_COLORS.hasOwnProperty(color)) {
+  if (Object.prototype.hasOwnProperty.call(PREFERRED_COLORS, color)) {
     const keys = PREFERRED_COLORS[color];
     for (let i = 0; i < keys.length; i++) {
       PREFERRED_COLORS_MAP[keys[i]] = color;
diff --git a/frontend/src/metabase/lib/noop.js b/frontend/src/metabase/lib/noop.js
new file mode 100644
index 0000000000000000000000000000000000000000..4f15c7b33f9f90d52a22bbab1ff56513afc20b27
--- /dev/null
+++ b/frontend/src/metabase/lib/noop.js
@@ -0,0 +1,4 @@
+// there's nothing here!
+
+// This file is the alternative to importing EE plugins.
+// Either way we import something to ensure a consisten import order.
diff --git a/frontend/src/metabase/lib/settings.js b/frontend/src/metabase/lib/settings.js
index 04783ae7e5f3f0622839f05cdcd99c069d7c2576..e9ed4d45d228ff264f83fe0d5b58f50c88404c9b 100644
--- a/frontend/src/metabase/lib/settings.js
+++ b/frontend/src/metabase/lib/settings.js
@@ -111,7 +111,11 @@ class Settings {
 
   docsUrl(page = "", anchor = "") {
     let { tag } = this.get("version", {});
-    if (!tag) {
+    if (/^v1\.\d+\.\d+$/.test(tag)) {
+      // if it's a normal EE version, link to the corresponding CE docs
+      tag = tag.replace("v1", "v0");
+    } else if (!tag || /v1/.test(tag)) {
+      // if there's no tag or it's an EE version that might not have a matching CE version, link to latest
       tag = "latest";
     }
     if (page) {
diff --git a/frontend/src/metabase/query_builder/components/NativeQueryEditor.css b/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
index edfcede91a701c36db016faf8901978e1c589dc8..e6d966d96e3d00f213e6b92b061f9ecab0bb4027 100644
--- a/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
+++ b/frontend/src/metabase/query_builder/components/NativeQueryEditor.css
@@ -51,3 +51,34 @@
 .NativeQueryEditor .ace_editor .ace_gutter {
   background-color: var(--color-bg-light);
 }
+
+/* ace UI element styling */
+
+/* overall dropdown */
+.ace_editor.ace_autocomplete {
+  border: none;
+  box-shadow: 0 2px 3px 2px rgba(0, 0, 0, 0.08);
+  border-radius: 4px;
+  background-color: white;
+  color: #4c5773;
+}
+
+.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line,
+.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
+  background-color: var(--color-brand-light);
+  border: none;
+  outline: none;
+}
+
+.ace_completion-highlight {
+  color: var(--color-brand);
+}
+
+.ace_editor.ace_autocomplete .ace_line {
+  font-weight: bold;
+  padding-left: 4px;
+}
+
+.ace_editor.ace_autocomplete .ace_completion-meta {
+  font-weight: 400;
+}
diff --git a/frontend/src/metabase/query_builder/components/filters/FilterPopoverHeader.jsx b/frontend/src/metabase/query_builder/components/filters/FilterPopoverHeader.jsx
index c611d146140554bb3849c083ac0708572024d0f7..9e399cabb5c5aafb3ca13f969c7f90bb5b48818b 100644
--- a/frontend/src/metabase/query_builder/components/filters/FilterPopoverHeader.jsx
+++ b/frontend/src/metabase/query_builder/components/filters/FilterPopoverHeader.jsx
@@ -34,7 +34,9 @@ export default function FilterPopoverHeader({
     >
       {showFieldPicker && (
         <SidebarHeader
-          className={cx("text-default py1")}
+          className={cx("text-default py1", {
+            pr2: !showOperatorSelectorOnOwnRow,
+          })}
           title={
             (field.table ? field.table.displayName() + " – " : "") +
             field.displayName()
diff --git a/frontend/src/metabase/visualizations/lib/settings/series.js b/frontend/src/metabase/visualizations/lib/settings/series.js
index 89611f539d9a96047db21f049448c015fde17bd0..68c44ec6d60c7545f627f0bc9de08d0c71d0bf5c 100644
--- a/frontend/src/metabase/visualizations/lib/settings/series.js
+++ b/frontend/src/metabase/visualizations/lib/settings/series.js
@@ -134,7 +134,7 @@ export function seriesSetting({
       widget: "toggle",
       getHidden: (single, seriesSettings, { settings, series }) =>
         series.length <= 1 || // no need to show series-level control if there's only one series
-        !settings.hasOwnProperty("graph.show_values") || // don't show it unless this chart has a global setting
+        !Object.prototype.hasOwnProperty.call(settings, "graph.show_values") || // don't show it unless this chart has a global setting
         settings["stackable.stack_type"], // hide series controls if the chart is stacked
       getDefault: (single, seriesSettings, { settings }) =>
         settings["graph.show_values"],
diff --git a/frontend/test/__runner__/run_cypress_tests.js b/frontend/test/__runner__/run_cypress_tests.js
index 76bb1f1d0fb33abe2cef48a3191eee570cecff16..8fcd9eab9f22672cebf02ad7e3b5ee68e3b57826 100644
--- a/frontend/test/__runner__/run_cypress_tests.js
+++ b/frontend/test/__runner__/run_cypress_tests.js
@@ -69,8 +69,16 @@ const init = async () => {
   let commandLineConfig = `baseUrl=${server.host}`;
   if (testFiles) {
     commandLineConfig = `${commandLineConfig},integrationFolder=${testFilesLocation}`;
+  } else {
+    // if we're not running specific tests, avoid including db and smoketests
+    commandLineConfig = `${commandLineConfig},ignoreTestFiles=**/metabase-{smoketest,db}/**`;
   }
 
+  // These env vars provide the token to the backend.
+  // If they're not present, we skip some tests that depend on a valid token.
+  const hasEnterpriseToken =
+    process.env["ENTERPRISE_TOKEN"] && process.env["ENABLE_ENTERPRISE_EDITION"];
+
   const cypressProcess = spawn(
     "yarn",
     [
@@ -92,6 +100,7 @@ const init = async () => {
             process.env["CYPRESS_GROUP"],
           ]
         : []),
+      ...(hasEnterpriseToken ? ["--env", "HAS_ENTERPRISE_TOKEN=true"] : []),
     ],
     { stdio: "inherit" },
   );
diff --git a/frontend/test/__support__/cypress.js b/frontend/test/__support__/cypress.js
index d5837f149cd61b4950a65d167766d8c6e7a8e823..dcf1cfe6981e92083fc06d0915fc73bee442b5bd 100644
--- a/frontend/test/__support__/cypress.js
+++ b/frontend/test/__support__/cypress.js
@@ -1,5 +1,7 @@
 import "@testing-library/cypress/add-commands";
 
+export const version = require("../../../version.json");
+
 export const USERS = {
   admin: {
     first_name: "Bobby",
@@ -175,6 +177,10 @@ export function createNativeQuestion(name, query) {
   });
 }
 
+export const describeWithToken = Cypress.env("HAS_ENTERPRISE_TOKEN")
+  ? describe
+  : describe.skip;
+
 // TODO: does this really need to be a global helper function?
 export function createBasicAlert({ firstAlert, includeNormal } = {}) {
   cy.get(".Icon-bell").click();
diff --git a/frontend/test/__support__/mocks.js b/frontend/test/__support__/mocks.js
index 43da1f4b82f9da0d31acb256762d40104a684ed6..35f1a889956c1ca9baf1cc0938079cdc5a4c471b 100644
--- a/frontend/test/__support__/mocks.js
+++ b/frontend/test/__support__/mocks.js
@@ -1,3 +1,5 @@
+/* eslint-disable no-import-assign*/
+
 global.ga = () => {};
 global.ace.define = () => {};
 global.ace.require = () => {};
diff --git a/frontend/test/cypress.json b/frontend/test/cypress.json
index 309aa12fd26cc9a4340aca66c9994a6878e576f7..4cd70d02f7f8a797539fbe047515b6c7ca5be44b 100644
--- a/frontend/test/cypress.json
+++ b/frontend/test/cypress.json
@@ -2,8 +2,12 @@
   "projectId": "a394u1",
   "testFiles": "**/*.cy.spec.js",
   "pluginsFile": "frontend/test/cypress-plugins.js",
-  "integrationFolder": "frontend/test/metabase",
+  "integrationFolder": ".",
   "supportFile": "frontend/test/__support__/cypress.js",
   "viewportHeight": 800,
-  "viewportWidth": 1280
+  "viewportWidth": 1280,
+  "retries": {
+    "runMode": 2,
+    "openMode": 0
+  }
 }
diff --git a/frontend/test/metabase/internal/__snapshots__/components.unit.spec.js.snap b/frontend/test/metabase/internal/__snapshots__/components.unit.spec.js.snap
index b1672d953456ad8ec5ce39c6f40555060bae8d88..880cef2a7028aacc7844a9e03ba0fe8d58999fcb 100644
--- a/frontend/test/metabase/internal/__snapshots__/components.unit.spec.js.snap
+++ b/frontend/test/metabase/internal/__snapshots__/components.unit.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`Button should render "" correctly 1`] = `
 <button
-  className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+  className="Button sc-bdVaJa bDWFJH flex-no-shrink"
 >
   <div
     className="flex layout-centered"
@@ -36,7 +36,7 @@ exports[`Button should render "primary" correctly 1`] = `
 
 exports[`Button should render "with an icon" correctly 1`] = `
 <button
-  className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+  className="Button sc-bdVaJa bDWFJH flex-no-shrink"
 >
   <div
     className="flex layout-centered"
@@ -72,7 +72,7 @@ exports[`ButtonBar should render "default" correctly 1`] = `
     className="mr-auto sc-htpNat cZLspC"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
@@ -100,7 +100,7 @@ exports[`ButtonBar should render "left and right" correctly 1`] = `
     className="mr-auto sc-htpNat cZLspC"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
@@ -118,7 +118,7 @@ exports[`ButtonBar should render "left and right" correctly 1`] = `
     className="ml-auto sc-htpNat cISmVk"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
@@ -143,7 +143,7 @@ exports[`ButtonBar should render "left, right, and center" correctly 1`] = `
     className="flex-full flex-basis-none sc-htpNat cZLspC"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
@@ -161,7 +161,7 @@ exports[`ButtonBar should render "left, right, and center" correctly 1`] = `
     className="sc-htpNat jncdfk"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
@@ -179,7 +179,7 @@ exports[`ButtonBar should render "left, right, and center" correctly 1`] = `
     className="flex-full flex-basis-none sc-htpNat cISmVk"
   >
     <button
-      className="Button sc-bdVaJa bDWFJH flex-no-shrink "
+      className="Button sc-bdVaJa bDWFJH flex-no-shrink"
     >
       <div
         className="flex layout-centered"
diff --git a/frontend/test/metabase/scenarios/admin/people/people.cy.spec.js b/frontend/test/metabase/scenarios/admin/people/people.cy.spec.js
index 90733d0f9aa578b5699981080fa3a56f517063e9..ba3031bf69ff3e51285dd83eb875de2164f089d8 100644
--- a/frontend/test/metabase/scenarios/admin/people/people.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/people/people.cy.spec.js
@@ -1,4 +1,4 @@
-import { signInAsAdmin, restore, main } from "__support__/cypress";
+import { signInAsAdmin, restore } from "__support__/cypress";
 
 describe("scenarios > admin > people", () => {
   before(restore);
@@ -7,13 +7,43 @@ describe("scenarios > admin > people", () => {
   const email = `testy${Math.round(Math.random() * 100000)}@metabase.com`;
 
   describe("user management", () => {
-    it("should render", () => {
+    it("should render (metabase-enterprise#210)", () => {
       cy.visit("/admin/people");
-      main().within(() => {
-        cy.findByText("People");
-        cy.findByText("Groups");
+
+      cy.log("**Assert it loads People by default**");
+      cy.get(".PageTitle").contains("People");
+
+      cy.get(".ContentTable tbody tr")
+        .as("result-rows")
+        // Bobby Tables, No Collection Tableton, No Data Tableton, None Tableton, Robert Tableton
+        .should("have.length", 5);
+
+      // A small sidebar selector
+      cy.get(".AdminList-items").within(() => {
+        cy.findByText("People").should("have.class", "selected");
+        cy.findByText("Groups").click();
+      });
+
+      cy.log("**Switch to 'Groups' and make sure it renders properly**");
+      cy.get(".PageTitle").contains("Groups");
+
+      // Administrators, All Users, collection, data
+      cy.get("@result-rows").should("have.length", 4);
+
+      cy.get(".AdminList-items").within(() => {
+        cy.findByText("Groups").should("have.class", "selected");
       });
+
+      cy.log(
+        "**Dig into one of the user groups and make sure its members are listed**",
+      );
+      cy.findByText("All Users").click();
+      cy.get(".PageTitle").contains("All Users");
+
+      // The same list as for "People"
+      cy.get("@result-rows").should("have.length", 5);
     });
+
     it("should allow admin to create new users", () => {
       cy.visit("/admin/people");
       cy.findByText("Add someone").click();
diff --git a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
index 17b69e80467c8430f03d5ec506adf3eb2fe125bf..0e7a44c6c83b95378aacd6aaeba1ca30db13df10 100644
--- a/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
+++ b/frontend/test/metabase/scenarios/admin/settings/settings.cy.spec.js
@@ -1,8 +1,9 @@
 import {
   signInAsAdmin,
   restore,
-  popover,
   openOrdersTable,
+  version,
+  popover,
 } from "__support__/cypress";
 
 describe("scenarios > admin > settings", () => {
@@ -172,94 +173,96 @@ describe("scenarios > admin > settings", () => {
     cy.contains(/^February 11, 2019, 9:40 PM$/);
   });
 
-  describe(" > embedding settings", () => {
-    it("should validate a premium embedding token has a valid format", () => {
-      cy.server();
-      cy.route("PUT", "/api/setting/premium-embedding-token").as(
-        "saveEmbeddingToken",
-      );
-
-      cy.visit("/admin/settings/embedding_in_other_applications");
-      cy.contains("Premium embedding");
-      cy.contains("Enter a token").click();
-
-      // Try an invalid token format
-      cy.contains("Enter the token")
-        .next()
-        .type("Hi")
-        .blur();
-      cy.wait("@saveEmbeddingToken").then(({ response }) => {
-        expect(response.body).to.equal(
-          "Token format is invalid. Token should be 64 hexadecimal characters.",
+  if (version.edition !== "enterprise") {
+    describe(" > embedding settings", () => {
+      it("should validate a premium embedding token has a valid format", () => {
+        cy.server();
+        cy.route("PUT", "/api/setting/premium-embedding-token").as(
+          "saveEmbeddingToken",
         );
-      });
-      cy.contains("Token format is invalid.");
-    });
 
-    it("should validate a premium embedding token exists", () => {
-      cy.server();
-      cy.route("PUT", "/api/setting/premium-embedding-token").as(
-        "saveEmbeddingToken",
-      );
-
-      cy.visit("/admin/settings/embedding_in_other_applications");
-      cy.contains("Premium embedding");
-      cy.contains("Enter a token").click();
+        cy.visit("/admin/settings/embedding_in_other_applications");
+        cy.contains("Premium embedding");
+        cy.contains("Enter a token").click();
+
+        // Try an invalid token format
+        cy.contains("Enter the token")
+          .next()
+          .type("Hi")
+          .blur();
+        cy.wait("@saveEmbeddingToken").then(({ response }) => {
+          expect(response.body).to.equal(
+            "Token format is invalid. Token should be 64 hexadecimal characters.",
+          );
+        });
+        cy.contains("Token format is invalid.");
+      });
 
-      // Try a valid format, but an invalid token
-      cy.contains("Enter the token")
-        .next()
-        .type(
-          "11397b1e60cfb1372f2f33ac8af234a15faee492bbf5c04d0edbad76da3e614a",
-        )
-        .blur();
-      cy.wait("@saveEmbeddingToken").then(({ response }) => {
-        expect(response.body).to.equal(
-          "Unable to validate token: 404 not found.",
+      it("should validate a premium embedding token exists", () => {
+        cy.server();
+        cy.route("PUT", "/api/setting/premium-embedding-token").as(
+          "saveEmbeddingToken",
         );
+
+        cy.visit("/admin/settings/embedding_in_other_applications");
+        cy.contains("Premium embedding");
+        cy.contains("Enter a token").click();
+
+        // Try a valid format, but an invalid token
+        cy.contains("Enter the token")
+          .next()
+          .type(
+            "11397b1e60cfb1372f2f33ac8af234a15faee492bbf5c04d0edbad76da3e614a",
+          )
+          .blur();
+        cy.wait("@saveEmbeddingToken").then(({ response }) => {
+          expect(response.body).to.equal(
+            "Unable to validate token: 404 not found.",
+          );
+        });
+        cy.contains("Unable to validate token: 404 not found.");
       });
-      cy.contains("Unable to validate token: 404 not found.");
-    });
 
-    it("should be able to set a premium embedding token", () => {
-      // A random embedding token with valid format
-      const embeddingToken =
-        "11397b1e60cfb1372f2f33ac8af234a15faee492bbf5c04d0edbad76da3e614a";
-
-      cy.server();
-      cy.route({
-        method: "PUT",
-        url: "/api/setting/premium-embedding-token",
-        response: embeddingToken,
-      }).as("saveEmbeddingToken");
-
-      cy.visit("/admin/settings/embedding_in_other_applications");
-      cy.contains("Premium embedding");
-      cy.contains("Enter a token").click();
-
-      cy.route("GET", "/api/session/properties").as("getSessionProperties");
-      cy.route({
-        method: "GET",
-        url: "/api/setting",
-        response: [
-          { key: "enable-embedding", value: true },
-          { key: "embedding-secret-key", value: embeddingToken },
-          { key: "premium-embedding-token", value: embeddingToken },
-        ],
-      }).as("getSettings");
-
-      cy.contains("Enter the token")
-        .next()
-        .type(embeddingToken)
-        .blur();
-      cy.wait("@saveEmbeddingToken").then(({ response }) => {
-        expect(response.body).to.equal(embeddingToken);
+      it("should be able to set a premium embedding token", () => {
+        // A random embedding token with valid format
+        const embeddingToken =
+          "11397b1e60cfb1372f2f33ac8af234a15faee492bbf5c04d0edbad76da3e614a";
+
+        cy.server();
+        cy.route({
+          method: "PUT",
+          url: "/api/setting/premium-embedding-token",
+          response: embeddingToken,
+        }).as("saveEmbeddingToken");
+
+        cy.visit("/admin/settings/embedding_in_other_applications");
+        cy.contains("Premium embedding");
+        cy.contains("Enter a token").click();
+
+        cy.route("GET", "/api/session/properties").as("getSessionProperties");
+        cy.route({
+          method: "GET",
+          url: "/api/setting",
+          response: [
+            { key: "enable-embedding", value: true },
+            { key: "embedding-secret-key", value: embeddingToken },
+            { key: "premium-embedding-token", value: embeddingToken },
+          ],
+        }).as("getSettings");
+
+        cy.contains("Enter the token")
+          .next()
+          .type(embeddingToken)
+          .blur();
+        cy.wait("@saveEmbeddingToken").then(({ response }) => {
+          expect(response.body).to.equal(embeddingToken);
+        });
+        cy.wait("@getSessionProperties");
+        cy.wait("@getSettings");
+        cy.contains("Premium embedding enabled");
       });
-      cy.wait("@getSessionProperties");
-      cy.wait("@getSettings");
-      cy.contains("Premium embedding enabled");
     });
-  });
+  }
 
   describe(" > email settings", () => {
     it("should be able to save email settings", () => {
diff --git a/frontend/test/metabase/scenarios/question/view.cy.spec.js b/frontend/test/metabase/scenarios/question/view.cy.spec.js
index 7d5011dd4bca0971ca20a63bae84ec4753d17582..e393df95336002b795922dd275a9a7d48e778012 100644
--- a/frontend/test/metabase/scenarios/question/view.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/view.cy.spec.js
@@ -215,7 +215,10 @@ describe("scenarios > question > view", () => {
         .first()
         .click();
       popover().within(() => {
-        cy.findByText("Widget").click();
+        cy.findByPlaceholderText("Enter some text")
+          .click()
+          .clear()
+          .type("Widget");
         cy.findByText("Add filter").click();
       });
       cy.get(".RunButton")
diff --git a/frontend/test/metabase/visualizations/__support__/visualizations.js b/frontend/test/metabase/visualizations/__support__/visualizations.js
index c0b8c360abfd1a50d3ba40f2442ba57d0d3b4fa5..98420ea05dffd28c4f34d4ca94640881c95fd2d5 100644
--- a/frontend/test/metabase/visualizations/__support__/visualizations.js
+++ b/frontend/test/metabase/visualizations/__support__/visualizations.js
@@ -145,7 +145,7 @@ export const MultiseriesLineCard = (name, ...overrides) =>
 function deepExtend(target, ...sources) {
   for (const source of sources) {
     for (const prop in source) {
-      if (source.hasOwnProperty(prop)) {
+      if (Object.prototype.hasOwnProperty.call(source, prop)) {
         if (
           target[prop] &&
           typeof target[prop] === "object" &&
diff --git a/frontend/test/metabase/visualizations/components/Visualization.unit.spec.js b/frontend/test/metabase/visualizations/components/Visualization.unit.spec.js
index 566ffa38a43dae5af93106ac3314af5c9a01713a..b07589cc5cd0845fde700938e17964ef1895b048 100644
--- a/frontend/test/metabase/visualizations/components/Visualization.unit.spec.js
+++ b/frontend/test/metabase/visualizations/components/Visualization.unit.spec.js
@@ -15,15 +15,12 @@ import Visualization from "metabase/visualizations/components/Visualization";
 
 describe("Visualization", () => {
   // eslint-disable-next-line no-unused-vars
-  let element, viz;
+  let element;
   const qs = s => element.querySelector(s);
   const qsa = s => [...element.querySelectorAll(s)];
 
   const renderViz = async series => {
-    ReactDOM.render(
-      <Visualization ref={ref => (viz = ref)} rawSeries={series} />,
-      element,
-    );
+    ReactDOM.render(<Visualization rawSeries={series} />, element);
     // The chart isn't rendered until the next tick. This is due to ExplicitSize
     // not setting the dimensions until after mounting.
     await delay(0);
diff --git a/jest.integ.conf.json b/jest.integ.conf.json
index d4cd5c18c6b06365388a79b71e2191c4e7f70930..0ea8246705bf7f69ac4f6bcdda25d6b45ce7a248 100644
--- a/jest.integ.conf.json
+++ b/jest.integ.conf.json
@@ -4,8 +4,16 @@
     "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/frontend/test/__mocks__/fileMock.js",
     "^promise-loader\\?global\\!metabase\\/lib\\/ga-metadata$": "<rootDir>/frontend/src/metabase/lib/ga-metadata.js"
   },
-  "testMatch": ["<rootDir>/frontend/test/**/*.integ.spec.js?(x)"],
-  "modulePaths": ["<rootDir>/frontend/test", "<rootDir>/frontend/src"],
+  "testMatch": [
+    "<rootDir>/frontend/test/**/*.integ.spec.js?(x)",
+    "<rootDir>/enterprise/frontend/test/**/*.integ.spec.js?(x)"
+  ],
+  "modulePaths": [
+    "<rootDir>/frontend/test",
+    "<rootDir>/frontend/src",
+    "<rootDir>/enterprise/frontend/test",
+    "<rootDir>/enterprise/frontend/src"
+  ],
   "setupFiles": [
     "<rootDir>/frontend/test/jest-setup.js",
     "<rootDir>/frontend/test/enzyme-setup.js",
diff --git a/jest.unit.conf.json b/jest.unit.conf.json
index 9b24cc7512a9163b8c86b52efc198fd2af524a2a..c9d41ff6b6d4209fffe06bea108635c6e512701b 100644
--- a/jest.unit.conf.json
+++ b/jest.unit.conf.json
@@ -5,8 +5,16 @@
     "^promise-loader\\?global\\!metabase\\/lib\\/ga-metadata$": "<rootDir>/frontend/src/metabase/lib/ga-metadata.js"
   },
   "testPathIgnorePatterns": ["<rootDir>/frontend/test/.*/.*.tz.unit.spec.js"],
-  "testMatch": ["<rootDir>/frontend/test/**/*.unit.spec.js?(x)"],
-  "modulePaths": ["<rootDir>/frontend/test", "<rootDir>/frontend/src"],
+  "testMatch": [
+    "<rootDir>/frontend/test/**/*.unit.spec.js?(x)",
+    "<rootDir>/enterprise/frontend/test/**/*.unit.spec.js?(x)"
+  ],
+  "modulePaths": [
+    "<rootDir>/frontend/test",
+    "<rootDir>/frontend/src",
+    "<rootDir>/enterprise/frontend/test",
+    "<rootDir>/enterprise/frontend/src"
+  ],
   "setupFiles": [
     "<rootDir>/frontend/test/jest-setup.js",
     "<rootDir>/frontend/test/enzyme-setup.js",
diff --git a/package.json b/package.json
index cedc1432763cc2aad8d2db928dac606198bbf33e..7a8fb4bb88ebb8c3adc37bc7a85857e94c591f30 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,7 @@
     "styled-system": "2.2.5",
     "system-components": "2.0.3",
     "tether": "^1.2.0",
-    "ttag": "^1.7.8",
+    "ttag": "1.7.15",
     "underscore": "^1.8.3",
     "webpack-dev-middleware": "^1.12.0",
     "z-index": "0.0.1"
diff --git a/project.clj b/project.clj
index 0e0a32edc0876a339dbac35a701e109e776c7ddb..836c27ffeda70f3b1c52da5b732a7bdce2399d44 100644
--- a/project.clj
+++ b/project.clj
@@ -2,7 +2,7 @@
 ;; full set of options are here .. https://github.com/technomancy/leiningen/blob/master/sample.project.clj
 
 (defproject metabase-core "1.0.0-SNAPSHOT"
-  :description      "Metabase Community Edition"
+  :description      "Metabase"
   :url              "https://metabase.com/"
   :min-lein-version "2.5.0"
 
@@ -12,11 +12,17 @@
                                         "-user" "" "-password" "" "-driver" "org.h2.Driver"]
    "generate-automagic-dashboards-pot" ["with-profile" "+generate-automagic-dashboards-pot" "run"]
    "install"                           ["with-profile" "+install" "install"]
+   "install-ee"                        ["with-profile" "+install,+ee" "install"]
    "install-for-building-drivers"      ["with-profile" "install-for-building-drivers" "install"]
+   "install-for-building-drivers-ee"   ["with-profile" "install-for-building-drivers,+ee" "install"]
    "run"                               ["with-profile" "+run" "run"]
+   "run-ee"                            ["with-profile" "+run,+ee" "run"]
    "run-with-repl"                     ["with-profile" "+run-with-repl" "repl"]
+   "run-with-repl-ee"                  ["with-profile" "+run-with-repl,+ee" "repl"]
    "ring"                              ["with-profile" "+ring" "ring"]
+   "ring-ee"                           ["with-profile" "+ring,+ee" "ring"]
    "test"                              ["with-profile" "+test" "test"]
+   "test-ee"                           ["with-profile" "+test,+ee" "test"]
    "bikeshed"                          ["with-profile" "+bikeshed" "bikeshed"
                                         "--max-line-length" "205"
                                         ;; see https://github.com/dakrone/lein-bikeshed/issues/41
@@ -29,8 +35,11 @@
    ;; `lein lint` will run all linters
    "lint"                              ["do" ["eastwood"] ["bikeshed"] ["check-namespace-decls"] ["docstring-checker"] ["cloverage"]]
    "repl"                              ["with-profile" "+repl" "repl"]
+   "repl-ee"                           ["with-profile" "+repl,+ee" "repl"]
    "strip-and-compress"                ["with-profile" "+strip-and-compress,-user,-dev" "run"]
-   "compare-h2-dbs"                    ["with-profile" "+compare-h2-dbs" "run"]}
+   "compare-h2-dbs"                    ["with-profile" "+compare-h2-dbs" "run"]
+   "uberjar"                           ["uberjar"]
+   "uberjar-ee"                        ["with-profile" "+ee" "uberjar"]}
 
   ;; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   ;; !!                                   PLEASE KEEP THESE ORGANIZED ALPHABETICALLY                                  !!
@@ -105,7 +114,10 @@
    [me.raynes/fs "1.4.6"]                                             ; Filesystem tools
    [medley "1.3.0"]                                                   ; lightweight lib of useful functions
    [metabase/connection-pool "1.1.1"]                                 ; simple wrapper around C3P0. JDBC connection pools
+   [metabase/saml20-clj "2.0.0"]                                      ; EE SAML integration
    [metabase/throttle "1.0.2"]                                        ; Tools for throttling access to API endpoints and other code pathways
+   [net.redhogs.cronparser/cron-parser-core "3.4"                     ; describe Cron schedule in human-readable language
+    :exclusions [org.slf4j/slf4j-api]]
    [net.sf.cssbox/cssbox "4.12" :exclusions [org.slf4j/slf4j-api]]    ; HTML / CSS rendering
    [org.apache.commons/commons-lang3 "3.10"]                          ; helper methods for working with java.lang stuff
    [org.apache.logging.log4j/log4j-api "2.13.3"]                      ; apache logging framework
@@ -174,10 +186,16 @@
   "metabase.jar"
 
   :profiles
-  {:dev
-   {:source-paths ["dev/src" "local/src"]
+  {:oss ; exists for symmetry with the ee profile
+   {}
 
-    :test-paths ["test" "backend/mbql/test"]
+   :ee
+   {:source-paths ["enterprise/backend/src"]
+    :test-paths   ["enterprise/backend/test"]}
+
+   :dev
+   {:source-paths ["dev/src" "local/src"]
+    :test-paths   ["test" "backend/mbql/test"]
 
     :dependencies
     [[clj-http-fake "1.0.3" :exclusions [slingshot]]                  ; Library to mock clj-http responses
@@ -293,7 +311,7 @@
      :mb-api-key      "test-api-key"
      ;; use a random port between 3001 and 3501. That way if you run multiple sets of tests at the same time locally
      ;; they won't stomp on each other
-     :mb-jetty-port   #=(eval (str (+ 3001 (rand-int 500))))}
+     :mb-jetty-port   #= (eval (str (+ 3001 (rand-int 500))))}
 
     :jvm-opts
     ["-Duser.timezone=UTC"
@@ -314,13 +332,21 @@
     ;; so running the tests doesn't give you different answers
     {:jvm-opts ["-Duser.timezone=UTC"]}]
 
-   :bikeshed
+   ;; shared stuff between all linter profiles.
+   :linters-common
    [:include-all-drivers
+    :ee
+    :test-common
+    ;; always use in-memory H2 database for linters
+    {:env {:mb-db-type "h2"}}]
+
+   :bikeshed
+   [:linters-common
     {:plugins
      [[lein-bikeshed "0.5.2"]]}]
 
    :eastwood
-   [:include-all-drivers
+   [:linters-common
     {:plugins
      [[jonase/eastwood "0.3.6" :exclusions [org.clojure/clojure]]]
 
@@ -338,7 +364,7 @@
                            ;; get them to work
                            #_:unused-fn-args
                            #_:unused-locals]
-      :exclude-linters    [; Turn this off temporarily until we finish removing self-deprecated functions & macros
+      :exclude-linters    [    ; Turn this off temporarily until we finish removing self-deprecated functions & macros
                            :deprecations
                            ;; this has a fit in libs that use Potemin `import-vars` such as `java-time`
                            :implicit-dependencies
@@ -348,11 +374,12 @@
    ;; run ./bin/reflection-linter to check for reflection warnings
    :reflection-warnings
    [:include-all-drivers
+    :ee
     {:global-vars {*warn-on-reflection* true}}]
 
    ;; Check that all public vars have docstrings. Run with 'lein docstring-checker'
    :docstring-checker
-   [:include-all-drivers
+   [:linters-common
     {:plugins
      [[docstring-checker "1.1.0"]]
 
@@ -362,28 +389,22 @@
                 #"^metabase\.http-client$"]}}]
 
    :check-namespace-decls
-   [:include-all-drivers
+   [:linters-common
     {:plugins               [[lein-check-namespace-decls "1.0.2"]]
-     :source-paths          ^:replace ["src" "backend/mbql/src" "test" "backend/mbql/test"]
      :check-namespace-decls {:prefix-rewriting true}}]
 
    :cloverage
    [:test-common
     {:dependencies [[camsaul/cloverage "1.2.1.1" :exclusions [riddley]]]
      :plugins      [[camsaul/lein-cloverage  "1.2.1.1"]]
-     :source-paths ^:replace ["src" "backend/mbql/src"]
-     :test-paths   ^:replace ["test" "backend/mbql/test"]
+     :source-paths ^:replace ["src" "backend/mbql/src" "enterprise/backend/src"]
+     :test-paths   ^:replace ["test" "backend/mbql/test" "enterprise/backend/test"]
      :cloverage    {:fail-threshold 69
                     :exclude-call
                     [;; don't instrument logging forms, since they won't get executed as part of tests anyway
                      ;; log calls expand to these
                      clojure.tools.logging/logf
-                     clojure.tools.logging/logp
-                     ;; defonce and defmulti forms get instrumented incorrectly and are false negatives
-                     ;; -- see https://github.com/cloverage/cloverage/issues/294. Once this issue is
-                     ;; fixed we can remove this exception.
-                     defonce
-                     defmulti]}}]
+                     clojure.tools.logging/logp]}}]
 
    ;; build the uberjar with `lein uberjar`
    :uberjar
diff --git a/resources/frontend_client/app/assets/img/attributes_illustration.png b/resources/frontend_client/app/assets/img/attributes_illustration.png
new file mode 100644
index 0000000000000000000000000000000000000000..018f0df7813967cab5edaec5d4ea2b0fa7c987e4
Binary files /dev/null and b/resources/frontend_client/app/assets/img/attributes_illustration.png differ
diff --git a/resources/frontend_client/app/assets/img/attributes_illustration@2x.png b/resources/frontend_client/app/assets/img/attributes_illustration@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3403c362c301086e041de53add537272b413f4d
Binary files /dev/null and b/resources/frontend_client/app/assets/img/attributes_illustration@2x.png differ
diff --git a/resources/frontend_client/app/assets/img/logo.svg b/resources/frontend_client/app/assets/img/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6bfd403b51bcdb09e84544d6e63a1086b56ee70a
--- /dev/null
+++ b/resources/frontend_client/app/assets/img/logo.svg
@@ -0,0 +1,6 @@
+<svg viewBox="0 0 66 85" width="32" height="32" fill="currentColor" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <path d="M46.8253288,70.4935014 C49.5764899,70.4935014 51.8067467,68.1774705 51.8067467,65.3205017 C51.8067467,62.4635329 49.5764899,60.147502 46.8253288,60.147502 C44.0741676,60.147502 41.8439108,62.4635329 41.8439108,65.3205017 C41.8439108,68.1774705 44.0741676,70.4935014 46.8253288,70.4935014 Z M32.8773585,84.9779005 C35.6285197,84.9779005 37.8587764,82.6618697 37.8587764,79.8049008 C37.8587764,76.947932 35.6285197,74.6319011 32.8773585,74.6319011 C30.1261973,74.6319011 27.8959405,76.947932 27.8959405,79.8049008 C27.8959405,82.6618697 30.1261973,84.9779005 32.8773585,84.9779005 Z M32.8773585,70.4935014 C35.6285197,70.4935014 37.8587764,68.1774705 37.8587764,65.3205017 C37.8587764,62.4635329 35.6285197,60.147502 32.8773585,60.147502 C30.1261973,60.147502 27.8959405,62.4635329 27.8959405,65.3205017 C27.8959405,68.1774705 30.1261973,70.4935014 32.8773585,70.4935014 Z M18.9293882,70.4935014 C21.6805494,70.4935014 23.9108062,68.1774705 23.9108062,65.3205017 C23.9108062,62.4635329 21.6805494,60.147502 18.9293882,60.147502 C16.1782271,60.147502 13.9479703,62.4635329 13.9479703,65.3205017 C13.9479703,68.1774705 16.1782271,70.4935014 18.9293882,70.4935014 Z M46.8253288,56.0091023 C49.5764899,56.0091023 51.8067467,53.6930714 51.8067467,50.8361026 C51.8067467,47.9791337 49.5764899,45.6631029 46.8253288,45.6631029 C44.0741676,45.6631029 41.8439108,47.9791337 41.8439108,50.8361026 C41.8439108,53.6930714 44.0741676,56.0091023 46.8253288,56.0091023 Z M18.9293882,56.0091023 C21.6805494,56.0091023 23.9108062,53.6930714 23.9108062,50.8361026 C23.9108062,47.9791337 21.6805494,45.6631029 18.9293882,45.6631029 C16.1782271,45.6631029 13.9479703,47.9791337 13.9479703,50.8361026 C13.9479703,53.6930714 16.1782271,56.0091023 18.9293882,56.0091023 Z M46.8253288,26.8995984 C49.5764899,26.8995984 51.8067467,24.5835675 51.8067467,21.7265987 C51.8067467,18.8696299 49.5764899,16.553599 46.8253288,16.553599 C44.0741676,16.553599 41.8439108,18.8696299 41.8439108,21.7265987 C41.8439108,24.5835675 44.0741676,26.8995984 46.8253288,26.8995984 Z M32.8773585,41.5247031 C35.6285197,41.5247031 37.8587764,39.2086723 37.8587764,36.3517034 C37.8587764,33.4947346 35.6285197,31.1787037 32.8773585,31.1787037 C30.1261973,31.1787037 27.8959405,33.4947346 27.8959405,36.3517034 C27.8959405,39.2086723 30.1261973,41.5247031 32.8773585,41.5247031 Z M32.8773585,10.3459994 C35.6285197,10.3459994 37.8587764,8.02996853 37.8587764,5.17299969 C37.8587764,2.31603085 35.6285197,0 32.8773585,0 C30.1261973,0 27.8959405,2.31603085 27.8959405,5.17299969 C27.8959405,8.02996853 30.1261973,10.3459994 32.8773585,10.3459994 Z M32.8773585,26.8995984 C35.6285197,26.8995984 37.8587764,24.5835675 37.8587764,21.7265987 C37.8587764,18.8696299 35.6285197,16.553599 32.8773585,16.553599 C30.1261973,16.553599 27.8959405,18.8696299 27.8959405,21.7265987 C27.8959405,24.5835675 30.1261973,26.8995984 32.8773585,26.8995984 Z M18.9293882,26.8995984 C21.6805494,26.8995984 23.9108062,24.5835675 23.9108062,21.7265987 C23.9108062,18.8696299 21.6805494,16.553599 18.9293882,16.553599 C16.1782271,16.553599 13.9479703,18.8696299 13.9479703,21.7265987 C13.9479703,24.5835675 16.1782271,26.8995984 18.9293882,26.8995984 Z" opacity="0.2">
+  </path>
+  <path d="M60.773299,70.4935014 C63.5244602,70.4935014 65.754717,68.1774705 65.754717,65.3205017 C65.754717,62.4635329 63.5244602,60.147502 60.773299,60.147502 C58.0221379,60.147502 55.7918811,62.4635329 55.7918811,65.3205017 C55.7918811,68.1774705 58.0221379,70.4935014 60.773299,70.4935014 Z M4.98141795,70.3527958 C7.73257912,70.3527958 9.96283591,68.0367649 9.96283591,65.1797961 C9.96283591,62.3228273 7.73257912,60.0067964 4.98141795,60.0067964 C2.23025679,60.0067964 0,62.3228273 0,65.1797961 C0,68.0367649 2.23025679,70.3527958 4.98141795,70.3527958 Z M60.773299,56.0091023 C63.5244602,56.0091023 65.754717,53.6930714 65.754717,50.8361026 C65.754717,47.9791337 63.5244602,45.6631029 60.773299,45.6631029 C58.0221379,45.6631029 55.7918811,47.9791337 55.7918811,50.8361026 C55.7918811,53.6930714 58.0221379,56.0091023 60.773299,56.0091023 Z M32.8773585,56.0091023 C35.6285197,56.0091023 37.8587764,53.6930714 37.8587764,50.8361026 C37.8587764,47.9791337 35.6285197,45.6631029 32.8773585,45.6631029 C30.1261973,45.6631029 27.8959405,47.9791337 27.8959405,50.8361026 C27.8959405,53.6930714 30.1261973,56.0091023 32.8773585,56.0091023 Z M4.98141795,55.8683967 C7.73257912,55.8683967 9.96283591,53.5523658 9.96283591,50.695397 C9.96283591,47.8384281 7.73257912,45.5223973 4.98141795,45.5223973 C2.23025679,45.5223973 0,47.8384281 0,50.695397 C0,53.5523658 2.23025679,55.8683967 4.98141795,55.8683967 Z M60.773299,41.5247031 C63.5244602,41.5247031 65.754717,39.2086723 65.754717,36.3517034 C65.754717,33.4947346 63.5244602,31.1787037 60.773299,31.1787037 C58.0221379,31.1787037 55.7918811,33.4947346 55.7918811,36.3517034 C55.7918811,39.2086723 58.0221379,41.5247031 60.773299,41.5247031 Z M46.8253288,41.5247031 C49.5764899,41.5247031 51.8067467,39.2086723 51.8067467,36.3517034 C51.8067467,33.4947346 49.5764899,31.1787037 46.8253288,31.1787037 C44.0741676,31.1787037 41.8439108,33.4947346 41.8439108,36.3517034 C41.8439108,39.2086723 44.0741676,41.5247031 46.8253288,41.5247031 Z M60.773299,26.8995984 C63.5244602,26.8995984 65.754717,24.5835675 65.754717,21.7265987 C65.754717,18.8696299 63.5244602,16.553599 60.773299,16.553599 C58.0221379,16.553599 55.7918811,18.8696299 55.7918811,21.7265987 C55.7918811,24.5835675 58.0221379,26.8995984 60.773299,26.8995984 Z M18.9293882,41.5247031 C21.6805494,41.5247031 23.9108062,39.2086723 23.9108062,36.3517034 C23.9108062,33.4947346 21.6805494,31.1787037 18.9293882,31.1787037 C16.1782271,31.1787037 13.9479703,33.4947346 13.9479703,36.3517034 C13.9479703,39.2086723 16.1782271,41.5247031 18.9293882,41.5247031 Z M4.98141795,41.3839975 C7.73257912,41.3839975 9.96283591,39.0679667 9.96283591,36.2109978 C9.96283591,33.354029 7.73257912,31.0379981 4.98141795,31.0379981 C2.23025679,31.0379981 0,33.354029 0,36.2109978 C0,39.0679667 2.23025679,41.3839975 4.98141795,41.3839975 Z M4.98141795,26.8995984 C7.73257912,26.8995984 9.96283591,24.5835675 9.96283591,21.7265987 C9.96283591,18.8696299 7.73257912,16.553599 4.98141795,16.553599 C2.23025679,16.553599 0,18.8696299 0,21.7265987 C0,24.5835675 2.23025679,26.8995984 4.98141795,26.8995984 Z">
+  </path>
+</svg>
diff --git a/resources/frontend_client/index_template.html b/resources/frontend_client/index_template.html
index c5660083d6437cb56afe0c3daa3f998acf695653..595774ca9936f4645c4840f5a37d827d9dd23c9b 100644
--- a/resources/frontend_client/index_template.html
+++ b/resources/frontend_client/index_template.html
@@ -20,9 +20,11 @@
     <meta name="base-href" content="{{{baseHref}}}" />
     <meta name="uri" content="{{{uri}}}" />
 
+    <link rel="icon" href="{{{favicon}}}" />
+
     {{{embedCode}}}
 
-    <title>Metabase</title>
+    <title>{{{applicationName}}}</title>
 
     <base href="{{{baseHref}}}" />
 
diff --git a/resources/migrations/000_migrations.yaml b/resources/migrations/000_migrations.yaml
index c9e4a80c512c415ecc775a8f326a773b783360e2..b8c0fae938bd789019992c4757017ba07fa19c93 100644
--- a/resources/migrations/000_migrations.yaml
+++ b/resources/migrations/000_migrations.yaml
@@ -4170,9 +4170,6 @@ databaseChangeLog:
                     nullable: false
                     references: report_card(id)
                     foreignKeyName: fk_gtap_card_id
-                    # This one is not ON DELETE CASCADE because we do not want people going around deleting Cards and
-                    # accidentally deleting their GTAPs that depend on them. However since Cards get archived instead
-                    # of deleted it shouldn't matter anyway
               - column:
                   name: attribute_remappings
                   type: text
diff --git a/resources/quartz.properties b/resources/quartz.properties
index 6e8d20dc9fa1cf3648b8dce4fcaf1040a98d2a5b..a39a1c48e87a148443d34f703f89153c174cda36 100644
--- a/resources/quartz.properties
+++ b/resources/quartz.properties
@@ -27,6 +27,16 @@ org.quartz.jobStore.isClustered = true
 # than not at all for such things)
 org.quartz.jobStore.misfireThreshold=900000
 
+# By default, Quartz will fire triggers up to a minute late without considering them to be misfired; if it cannot fire
+# anything within that period for one reason or another (such as all threads in the thread pool being tied up), the
+# trigger is considered misfired. Threshold is in milliseconds.
+#
+# Default threshould is one minute (60,000)
+# We'll bump it up to 15 minutes (900,000) because the sorts of things we're scheduling aren't extremely time-sensitive,
+# for example Pulses and Sync can be sent out more than a minute late without issue. (In fact, 15 minutes late is better
+# than not at all for such things)
+org.quartz.jobStore.misfireThreshold=900000
+
 # Useful for debugging when Quartz jobs run and when they misfire
 #org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
 #org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger \{1\}.\{0\} fired job \{6\}.\{5\} at: \{4, date, HH:mm:ss MM/dd/yyyy}
diff --git a/src/metabase/api/automagic_dashboards.clj b/src/metabase/api/automagic_dashboards.clj
index 4e2aa4d3b60114b4733a3237cfa6946c6badd68e..07d19207a6eddcbce8fe153d81db6615490916e0 100644
--- a/src/metabase/api/automagic_dashboards.clj
+++ b/src/metabase/api/automagic_dashboards.clj
@@ -68,7 +68,7 @@
 
 (defn- adhoc-query-read-check
   [query]
-  (api/check-403 (perms/set-has-full-permissions-for-set?
+  (api/check-403 (perms/set-has-partial-permissions-for-set?
                    @api/*current-user-permissions-set*
                    (query-perms/perms-set (:dataset_query query), :throw-exceptions? true)))
   query)
diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index 363481d070f2082f5e7e6ff2f22511fc27f0d609..cd5dfc81b082d3ef0fcf9a50c0c8d0221ac8e185 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -523,7 +523,6 @@
           (reverse (range starting-position (+ (count sorted-cards) starting-position)))
           (reverse sorted-cards)))))
 
-
 (defn- move-cards-to-collection! [new-collection-id-or-nil card-ids]
   ;; if moving to a collection, make sure we have write perms for it
   (when new-collection-id-or-nil
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index b8df63a1561597f367d244706019a300e29abb6a..9b277c9d167e334aefef6465f3f740edd02c1a5e 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -196,16 +196,28 @@
   [dashboard]
   (update dashboard :ordered_cards add-query-average-duration-to-dashcards))
 
+(defn- hydrate-non-sandboxed-param-values
+  [{:keys [param_fields] :as dashboard}]
+  ;; We need to do this manually to ensure sandboxing is respected.
+  ;; If the user doesn't have full read access, assume they are sandboxed
+  (assoc dashboard :param_values (->> param_fields
+                                      vals
+                                      (filter mi/can-read?)
+                                      (map u/get-id)
+                                      set
+                                      params/field-ids->param-field-values
+                                      not-empty)))
 
 (defn- get-dashboard
   "Get Dashboard with ID."
   [id]
   (-> (Dashboard id)
       api/check-404
-      (hydrate [:ordered_cards :card :series] :can_write :param_fields :param_values)
+      (hydrate [:ordered_cards :card :series] :can_write :param_fields)
       api/read-check
       api/check-not-archived
       hide-unreadable-cards
+      hydrate-non-sandboxed-param-values
       add-query-average-durations))
 
 
diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj
index 99402dc084067b1501f30257d18993e296cdac42..1064d9cc19231a1484c4d506c8ea22262c631769 100644
--- a/src/metabase/api/dataset.clj
+++ b/src/metabase/api/dataset.clj
@@ -20,7 +20,7 @@
              [constraints :as qp.constraints]
              [permissions :as qp.perms]]
             [metabase.util
-             [i18n :refer [trs]]
+             [i18n :refer [trs tru]]
              [schema :as su]]
             [schema.core :as s]))
 
@@ -39,10 +39,14 @@
 
 (api/defendpoint ^:streaming POST "/"
   "Execute a query and retrieve the results in the usual format."
-  [:as {{:keys [database], :as query} :body}]
-  {database s/Int}
+  [:as {{:keys [database], query-type :type, :as query} :body}]
+  {database (s/maybe s/Int)}
+  ;; database is required unless this is an `internal` query.
   ;; don't permissions check the 'database' if it's the virtual database. That database doesn't actually exist :-)
-  (when-not (= database mbql.s/saved-questions-virtual-database-id)
+  (when (and (not= query-type "internal")
+             (not= database mbql.s/saved-questions-virtual-database-id))
+    (when-not database
+      (throw (Exception. (str (tru "`database` is required for all queries whose type is not `internal`.")))))
     (api/read-check Database database))
   ;; add sensible constraints for results limits on our query
   (let [source-card-id (query->source-card-id query)
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index f2fbd191a713267408a0aa9ad069f6fd0cc51c89..667fcb66f93c23c6a699bfeb227bd2850dd67cd7 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -1,5 +1,6 @@
 (ns metabase.api.field
-  (:require [clojure.tools.logging :as log]
+  (:require [clojure.core.memoize :as memoize]
+            [clojure.tools.logging :as log]
             [compojure.core :refer [DELETE GET POST PUT]]
             [metabase
              [query-processor :as qp]
@@ -11,6 +12,8 @@
              [dimension :refer [Dimension]]
              [field :as field :refer [Field]]
              [field-values :as field-values :refer [FieldValues]]
+             [interface :as mi]
+             [permissions :as perms]
              [table :refer [Table]]]
             [metabase.util
              [i18n :refer [trs]]
@@ -32,12 +35,36 @@
   "Schema for a valid `Field` visibility type."
   (apply s/enum (map name field/visibility-types)))
 
+(defn- has-segmented-query-permissions?
+  "Does the Current User have segmented query permissions for `table`?"
+  [table]
+  (perms/set-has-full-permissions? @api/*current-user-permissions-set*
+    (perms/table-segmented-query-path table)))
+
+(defn- throw-if-no-read-or-segmented-perms
+  "Validates that the user either has full read permissions for `field` or segmented permissions on the table
+  associated with `field`. Throws an exception that will return a 403 if not."
+  [field]
+  (when-not (or (mi/can-read? field)
+                (has-segmented-query-permissions? (field/table field)))
+    (api/throw-403)))
 
 (api/defendpoint GET "/:id"
   "Get `Field` with ID."
   [id]
-  (-> (api/read-check Field id)
-      (hydrate [:table :db] :has_field_values :dimensions :name_field)))
+  (let [field (-> (api/check-404 (Field id))
+                  (hydrate [:table :db] :has_field_values :dimensions :name_field))]
+    ;; Normal read perms = normal access.
+    ;;
+    ;; There's also aspecial case where we allow you to fetch a Field even if you don't have full read permissions for
+    ;; it: if you have segmented query access to the Table it belongs to. In this case, we'll still let you fetch the
+    ;; Field, since this is required to power features like Dashboard filters, but we'll treat this Field a little
+    ;; differently in other endpoints such as the FieldValues fetching endpoint.
+    ;;
+    ;; Check for permissions and throw 403 if we don't have them...
+    (throw-if-no-read-or-segmented-perms field)
+    ;; ...but if we do, return the Field <3
+    field))
 
 (defn- clear-dimension-on-fk-change! [{{dimension-id :id dimension-type :type} :dimensions :as field}]
   (when (and dimension-id (= :external dimension-type))
@@ -168,11 +195,41 @@
         (dissoc :human_readable_values :created_at :updated_at :id))
     {:values [], :field_id (:id field)}))
 
+(def ^:private ^{:arglist '([user-id last-updated field])} fetch-sandboxed-field-values*
+  (memoize/ttl
+   (fn [_ _ field]
+     {:values   (map vector (field-values/distinct-values field))
+      :field_id (u/get-id field)})
+   ;; Expire entires older than 30 days so we don't have entries for users and/or fields that
+   ;; no longer exists hanging around.
+   ;; (`clojure.core.cache/TTLCacheQ` (which `memoize` uses underneath) evicts all stale entries on
+   ;; every cache miss)
+   :ttl/threshold (* 1000 60 60 24 30)))
+
+(defn- fetch-sandboxed-field-values
+  [field]
+  (fetch-sandboxed-field-values*
+   api/*current-user-id*
+   (db/select-one-field :updated_at FieldValues :field_id (u/get-id field))
+   field))
+
 (api/defendpoint GET "/:id/values"
   "If a Field's value of `has_field_values` is `list`, return a list of all the distinct values of the Field, and (if
   defined by a User) a map of human-readable remapped values."
   [id]
-  (field->values (api/read-check Field id)))
+  (let [field (api/check-404 (Field id))]
+    (cond
+      ;; if you have normal read permissions, return normal results
+      (mi/can-read? field)
+      (field->values (api/read-check field))
+
+      ;; otherwise if you have Segmented query perms (but not normal read perms) we'll do an ad-hoc query to fetch the
+      ;; results, filtered by your GTAP
+      (has-segmented-query-permissions? (field/table field))
+      (fetch-sandboxed-field-values field)
+
+      :else
+      (api/throw-403))))
 
 ;; match things like GET /field-literal%2Ccreated_at%2Ctype%2FDatetime/values
 ;; (this is how things like [field-literal,created_at,type/Datetime] look when URL-encoded)
@@ -214,7 +271,7 @@
   "Update the fields values and human-readable values for a `Field` whose special type is
   `category`/`city`/`state`/`country` or whose base type is `type/Boolean`. The human-readable values are optional."
   [id :as {{value-pairs :values} :body}]
-  {value-pairs [[(s/one (s/maybe s/Num) "value") (s/optional su/NonBlankString "human readable value")]]}
+  {value-pairs [[(s/one s/Any "value") (s/optional su/NonBlankString "human readable value")]]}
   (let [field (api/write-check Field id)]
     (api/check (field-values/field-should-have-field-values? field)
       [400 (str "You can only update the human readable values of a mapped values of a Field whose value of "
@@ -318,10 +375,13 @@
   [id search-id value limit]
   {value su/NonBlankString
    limit (s/maybe su/IntStringGreaterThanZero)}
-  (let [field        (api/read-check Field id)
-        search-field (api/read-check Field search-id)]
+  (let [field        (api/check-404 (Field id))
+        search-field (api/check-404 (Field search-id))]
+    (throw-if-no-read-or-segmented-perms field)
+    (throw-if-no-read-or-segmented-perms search-field)
     (search-values field search-field value (when limit (Integer/parseInt limit)))))
 
+
 (defn remapped-value
   "Search for one specific remapping where the value of `field` exactly matches `value`. Returns a pair like
 
diff --git a/src/metabase/api/metastore.clj b/src/metabase/api/metastore.clj
new file mode 100644
index 0000000000000000000000000000000000000000..cf5ddd1bbe4676dc82db06229f82cf68de497c9e
--- /dev/null
+++ b/src/metabase/api/metastore.clj
@@ -0,0 +1,12 @@
+(ns metabase.api.metastore
+  (:require [compojure.core :refer [GET]]
+            [metabase.api.common :as api]
+            [metabase.public-settings.metastore :as metastore]))
+
+(api/defendpoint GET "/token/status"
+  "Fetch info about the current MetaStore premium features token including whether it is `valid`, a `trial` token, its
+  `features`, and when it is `valid_thru`."
+  []
+  (metastore/fetch-token-status (api/check-404 (metastore/premium-embedding-token))))
+
+(api/define-routes api/+check-superuser)
diff --git a/src/metabase/api/pulse.clj b/src/metabase/api/pulse.clj
index 04452116f0eb5bb702404fbd3774ed4a5edb11ba..a2d7dee60f5ae2bb959c52b70581279bdbf825f3 100644
--- a/src/metabase/api/pulse.clj
+++ b/src/metabase/api/pulse.clj
@@ -18,6 +18,7 @@
              [pulse :as pulse :refer [Pulse]]
              [pulse-channel :refer [channel-types PulseChannel]]
              [pulse-channel-recipient :refer [PulseChannelRecipient]]]
+            [metabase.plugins.classloader :as classloader]
             [metabase.pulse.render :as render]
             [metabase.util
              [i18n :refer [tru]]
@@ -29,6 +30,8 @@
              [hydrate :refer [hydrate]]])
   (:import java.io.ByteArrayInputStream))
 
+(u/ignore-exceptions (classloader/require 'metabase-enterprise.sandbox.api.util))
+
 (api/defendpoint GET "/"
   "Fetch all Pulses"
   [archived]
@@ -121,10 +124,17 @@
   (let [chan-types (-> channel-types
                        (assoc-in [:slack :configured] (slack/slack-configured?))
                        (assoc-in [:email :configured] (email/email-configured?)))]
-    {:channels (if-not (get-in chan-types [:slack :configured])
+    {:channels (cond
+                 (when-let [segmented-user? (resolve 'metabase-enterprise.sandbox.api.util/segmented-user?)]
+                   (segmented-user?))
+                 (dissoc chan-types :slack)
+
                  ;; no Slack integration, so we are g2g
+                 (not (get-in chan-types [:slack :configured]))
                  chan-types
+
                  ;; if we have Slack enabled build a dynamic list of channels/users
+                 :else
                  (try
                    (let [slack-channels (for [channel (slack/conversations-list)]
                                           (str \# (:name channel)))
@@ -191,7 +201,7 @@
    collection_id       (s/maybe su/IntGreaterThanZero)
    collection_position (s/maybe su/IntGreaterThanZero)}
   (check-card-read-permissions cards)
-  (p/send-pulse! body)
+  (p/send-pulse! (assoc body :creator_id api/*current-user-id*))
   {:ok true})
 
 (api/defendpoint DELETE "/:id/subscription/email"
diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj
index d73990ad582b8cc226d720ee0f0586385c1c25fc..01847594e081ffa94672e1c934f06aa1ba762d1e 100644
--- a/src/metabase/api/routes.clj
+++ b/src/metabase/api/routes.clj
@@ -2,6 +2,9 @@
   (:require [compojure
              [core :refer [context defroutes]]
              [route :as route]]
+            [metabase
+             [config :as config]
+             [util :as u]]
             [metabase.api
              [activity :as activity]
              [alert :as alert]
@@ -16,6 +19,7 @@
              [field :as field]
              [geojson :as geojson]
              [ldap :as ldap]
+             [metastore :as metastore]
              [metric :as metric]
              [native-query-snippet :as native-query-snippet]
              [notify :as notify]
@@ -37,12 +41,14 @@
              [transform :as transform]
              [user :as user]
              [util :as util]]
-            [metabase.config :as config]
             [metabase.middleware
              [auth :as middleware.auth]
              [exceptions :as middleware.exceptions]]
+            [metabase.plugins.classloader :as classloader]
             [metabase.util.i18n :refer [deferred-tru]]))
 
+(u/ignore-exceptions (classloader/require '[metabase-enterprise.sandbox.api.routes :as ee.sandbox.routes]))
+
 (def ^:private +generic-exceptions
   "Wrap `routes` so any Exception thrown is just returned as a generic 400, to prevent details from leaking in public
   endpoints."
@@ -62,6 +68,9 @@
   middleware.auth/enforce-authentication)
 
 (defroutes ^{:doc "Ring routes for API endpoints."} routes
+  (or (some-> (resolve 'ee.sandbox.routes/routes) var-get)
+      (fn [_ respond _]
+        (respond nil)))
   (context "/activity"             [] (+auth activity/routes))
   (context "/alert"                [] (+auth alert/routes))
   (context "/automagic-dashboards" [] (+auth magic/routes))
@@ -75,6 +84,7 @@
   (context "/field"                [] (+auth field/routes))
   (context "/geojson"              [] geojson/routes)
   (context "/ldap"                 [] (+auth ldap/routes))
+  (context "/metastore"            [] (+auth metastore/routes))
   (context "/metric"               [] (+auth metric/routes))
   (context "/native-query-snippet" [] (+auth native-query-snippet/routes))
   (context "/notify"               [] (+apikey notify/routes))
diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index a4d51d6812b16c48b4f90a28e90fab0d31fe0684..a8805a270fa53937d899328e66d36a658b4cd092 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -24,13 +24,15 @@
              [schema :as su]]
             [schema.core :as s]
             [throttle.core :as throttle]
-            [toucan.db :as db])
+            [toucan
+             [db :as db]
+             [models :as t.models]])
   (:import com.unboundid.util.LDAPSDKException
            java.util.UUID))
 
 (defmulti create-session!
   "Generate a new Session for a User. `session-type` is the currently either `:password` (for email + password login) or
-  `:sso` (for other login types). Returns the newly generated session `UUID`."
+  `:sso` (for other login types). Returns the newly generated Session."
   {:arglists '(^java.util.UUID [session-type user])}
   (fn [session-type & _]
     session-type))
@@ -40,14 +42,27 @@
    :last_login s/Any
    s/Keyword   s/Any})
 
-(s/defmethod create-session! :sso
+(s/defmethod create-session! :sso :- {:id UUID, :type (s/enum :normal :full-app-embed) s/Keyword s/Any}
   [_, user :- CreateSessionUserInfo]
-  (u/prog1 (UUID/randomUUID)
-    (db/insert! Session
-      :id      (str <>)
-      :user_id (u/get-id user))
+  (let [session-uuid (UUID/randomUUID)
+        session      (or
+                      (db/insert! Session
+                        :id      (str session-uuid)
+                        :user_id (u/get-id user))
+                      ;; HACK !!! For some reason `db/insert~ doesn't seem to be working correctly for Session.
+                      (t.models/post-insert (Session (str session-uuid))))]
+    (assert (map? session))
     (events/publish-event! :user-login
-      {:user_id (:id user), :session_id (str <>), :first_login (nil? (:last_login user))})))
+      {:user_id (u/get-id user), :session_id (str session-uuid), :first_login (nil? (:last_login user))})
+    (assoc session :id session-uuid)))
+
+(s/defmethod create-session! :password :- {:id UUID, :type (s/enum :normal :full-app-embed), s/Keyword s/Any}
+  [session-type, user :- CreateSessionUserInfo]
+  ;; this is actually the same as `create-session!` for `:sso` but we check whether password login is enabled.
+  (when-not (public-settings/enable-password-login)
+    (throw (UnsupportedOperationException. (str (tru "Password login is disabled for this instance.")))))
+  ((get-method create-session! :sso) session-type user))
+
 
 (s/defmethod create-session! :password
   [session-type, user :- CreateSessionUserInfo]
@@ -69,7 +84,7 @@
 (def ^:private disabled-account-message (deferred-tru "Your account is disabled. Please contact your administrator."))
 (def ^:private disabled-account-snippet (deferred-tru "Your account is disabled."))
 
-(s/defn ^:private ldap-login :- (s/maybe UUID)
+(s/defn ^:private ldap-login :- (s/maybe {:id UUID, s/Keyword s/Any})
   "If LDAP is enabled and a matching user exists return a new Session for them, or `nil` if they couldn't be
   authenticated."
   [username password]
@@ -87,7 +102,7 @@
       (catch LDAPSDKException e
         (log/error e (trs "Problem connecting to LDAP server, will fall back to local authentication"))))))
 
-(s/defn ^:private email-login :- (s/maybe UUID)
+(s/defn ^:private email-login :- (s/maybe {:id UUID, s/Keyword s/Any})
   "Find a matching `User` if one exists and return a new Session for them, or `nil` if they couldn't be authenticated."
   [username password]
   (when-let [user (db/select-one [User :id :password_salt :password :last_login], :%lower.email (u/lower-case-en username), :is_active true)]
@@ -102,7 +117,7 @@
   (when-not throttling-disabled?
     (throttle/check throttler throttle-key)))
 
-(s/defn ^:private login :- UUID
+(s/defn ^:private login :- {:id UUID, :type (s/enum :normal :full-app-embed), s/Keyword s/Any}
   "Attempt to login with different avaialable methods with `username` and `password`, returning new Session ID or
   throwing an Exception if login could not be completed."
   [username :- su/NonBlankString, password :- su/NonBlankString]
@@ -122,13 +137,6 @@
   (or (some->> (public-settings/source-address-header) (get headers))
       remote-addr))
 
-(defn- do-login
-  "Logs user in and creates an appropriate Ring response containing the newly created session's ID."
-  [username password request]
-  (let [session-id (login username password)
-        response   {:id session-id}]
-    (mw.session/set-session-cookie request response session-id)))
-
 (defn- do-http-400-on-error [f]
   (try
     (f)
@@ -146,13 +154,17 @@
   [:as {{:keys [username password]} :body, :as request}]
   {username su/NonBlankString
    password su/NonBlankString}
-  (let [request-source (source-address request)]
+  (let [request-source (source-address request)
+        do-login (fn []
+                   (let [{session-uuid :id, :as session} (login username password)
+                         response                        {:id (str session-uuid)}]
+                     (mw.session/set-session-cookie request response session)))]
     (if throttling-disabled?
-      (do-login username password request)
+      (do-login)
       (http-400-on-error
         (throttle/with-throttling [(login-throttlers :ip-address) request-source
                                    (login-throttlers :username)   username]
-          (do-login username password request))))))
+          (do-login))))))
 
 
 (api/defendpoint DELETE "/"
@@ -222,12 +234,10 @@
         (when-not (:last_login user)
           (email/send-user-joined-admin-notification-email! (User user-id)))
         ;; after a successful password update go ahead and offer the client a new session that they can use
-        (let [session-id (create-session! :password user)]
-          (mw.session/set-session-cookie
-           request
-           {:success    true
-            :session_id (str session-id)}
-           session-id)))
+        (let [{session-uuid :id, :as session} (create-session! :password user)
+              response                        {:success    true
+                                               :session_id (str session-uuid)}]
+          (mw.session/set-session-cookie request response session)))
       (api/throw-invalid-param-exception :password (tru "Invalid reset token"))))
 
 (api/defendpoint GET "/password_reset_token_valid"
@@ -309,7 +319,7 @@
   ;; things hairy and only enforce those for non-Google Auth users
   (user/create-new-google-auth-user! new-user))
 
-(s/defn ^:private google-auth-fetch-or-create-user! :- (s/maybe UUID)
+(s/defn ^:private google-auth-fetch-or-create-user! :- (s/maybe {:id UUID, s/Keyword s/Any})
   [first-name last-name email]
   (when-let [user (or (db/select-one [User :id :last_login] :%lower.email (u/lower-case-en email))
                       (google-auth-create-new-user! {:first_name first-name
@@ -319,15 +329,16 @@
 
 (defn do-google-auth
   "Call to Google to perform an authentication"
-  [{{:keys [token]} :body :as request}]
+  [{{:keys [token]} :body, :as request}]
   (let [token-info-response                    (http/post (format google-auth-token-info-url token))
         {:keys [given_name family_name email]} (google-auth-token-info token-info-response)]
     (log/info (trs "Successfully authenticated Google Auth token for: {0} {1}" given_name family_name))
-    (let [session-id (api/check-500 (google-auth-fetch-or-create-user! given_name family_name email))
-          response   {:id session-id}
-          user       (db/select-one [User :id :is_active], :email email)]
+    (let [{session-uuid :id, :as session} (api/check-500
+                                           (google-auth-fetch-or-create-user! given_name family_name email))
+          response                        {:id (str session-uuid)}
+          user                            (db/select-one [User :id :is_active], :email email)]
       (if (and user (:is_active user))
-        (mw.session/set-session-cookie request response session-id)
+        (mw.session/set-session-cookie request response session)
         (throw (ex-info (str disabled-account-message)
                         {:status-code 400
                          :errors      {:account disabled-account-snippet}}))))))
diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj
index 01c61bc068594c9cd12c3cc238b077b2f4554866..0962dd99f4fc8607031bd47c88af7a51c68db9f1 100644
--- a/src/metabase/api/setup.clj
+++ b/src/metabase/api/setup.clj
@@ -28,7 +28,9 @@
              [i18n :as i18n :refer [tru]]
              [schema :as su]]
             [schema.core :as s]
-            [toucan.db :as db])
+            [toucan
+             [db :as db]
+             [models :as t.models]])
   (:import java.util.UUID))
 
 (def ^:private SetupToken
@@ -39,20 +41,22 @@
 (defn- setup-create-user! [{:keys [email first-name last-name password]}]
   (let [session-id (str (UUID/randomUUID))
         new-user   (db/insert! User
-                     :email        email
-                     :first_name   first-name
-                     :last_name    last-name
-                     :password     (str (UUID/randomUUID))
-                     :is_superuser true)
+                               :email        email
+                               :first_name   first-name
+                               :last_name    last-name
+                               :password     (str (UUID/randomUUID))
+                               :is_superuser true)
         user-id    (u/get-id new-user)]
     ;; this results in a second db call, but it avoids redundant password code so figure it's worth it
     (user/set-password! user-id password)
     ;; then we create a session right away because we want our new user logged in to continue the setup process
-    (db/insert! Session
-      :id      session-id
-      :user_id user-id)
-    ;; return user ID and session ID
-    {:session-id session-id, :user-id user-id}))
+    (let [session (or (db/insert! Session
+                                  :id      session-id
+                                  :user_id user-id)
+                      ;; HACK -- Toucan doesn't seem to work correctly with models with string IDs
+                      (t.models/post-insert (Session (str session-id))))]
+      ;; return user ID, session ID, and the Session object itself
+      {:session-id session-id, :user-id user-id, :session session})))
 
 (defn- setup-create-database!
   "Create a new Database. Returns newly created Database."
@@ -121,11 +125,11 @@
                 ;; this because there is `io!` in this block
                 (setting.cache/restore-cache!)
                 (throw e))))]
-    (let [{:keys [user-id session-id database]} (create!)]
+    (let [{:keys [user-id session-id database session]} (create!)]
       (events/publish-event! :database-create database)
       (events/publish-event! :user-login {:user_id user-id, :session_id session-id, :first_login true})
       ;; return response with session ID and set the cookie as well
-      (mw.session/set-session-cookie request {:id session-id} (UUID/fromString session-id)))))
+      (mw.session/set-session-cookie request {:id session-id} session))))
 
 (api/defendpoint POST "/validate"
   "Validate that we can connect to a database given a set of details."
diff --git a/src/metabase/api/table.clj b/src/metabase/api/table.clj
index 9150e85f594b5cc7fc58122870502a167ced393d..b2ea0ef421421b69d08756da3c546cb781cd687b 100644
--- a/src/metabase/api/table.clj
+++ b/src/metabase/api/table.clj
@@ -239,23 +239,23 @@
                 field)))))
 
 (defn fetch-query-metadata
-  "Returns the query metadata used to power the query builder for the given table `table-or-table-id`"
-  [table include_sensitive_fields include_hidden_fields]
+  "Returns the query metadata used to power the Query Builder for the given `table`. `include-sensitive-fields?` and
+  `include-hidden-fields?` can be either booleans or boolean strings."
+  [table include-sensitive-fields? include-hidden-fields?]
   (api/read-check table)
-  (let [driver (driver.u/database->driver (:db_id table))]
+  (let [driver                    (driver.u/database->driver (:db_id table))
+        include-sensitive-fields? (cond-> include-sensitive-fields? (string? include-sensitive-fields?) Boolean/parseBoolean)
+        include-hidden-fields?    (cond-> include-hidden-fields? (string? include-hidden-fields?) Boolean/parseBoolean)]
     (-> table
         (hydrate :db [:fields [:target :has_field_values] :dimensions :has_field_values] :segments :metrics)
         (m/dissoc-in [:db :details])
         (assoc-dimension-options driver)
         format-fields-for-response
-        (update :fields
-                (let [hidden    (Boolean/parseBoolean include_hidden_fields)
-                      sensitive (Boolean/parseBoolean include_sensitive_fields)]
-                  (partial filter (fn [{:keys [visibility_type]}]
-                                    (case (keyword visibility_type)
-                                      :hidden    hidden
-                                      :sensitive sensitive
-                                      true))))))))
+        (update :fields (partial filter (fn [{visibility-type :visibility_type}]
+                                          (case (keyword visibility-type)
+                                            :hidden    include-hidden-fields?
+                                            :sensitive include-sensitive-fields?
+                                            true)))))))
 
 (api/defendpoint GET "/:id/query_metadata"
   "Get metadata about a `Table` useful for running queries.
diff --git a/src/metabase/api/user.clj b/src/metabase/api/user.clj
index daa9876b4694dd113f622a0ba89fb68a513c6be6..2b2e68d62fc7904ad0cae49d2a916efa1c753a2c 100644
--- a/src/metabase/api/user.clj
+++ b/src/metabase/api/user.clj
@@ -12,6 +12,7 @@
              [collection :as collection :refer [Collection]]
              [permissions-group :as group]
              [user :as user :refer [User]]]
+            [metabase.plugins.classloader :as classloader]
             [metabase.util :as u]
             [metabase.util
              [i18n :as i18n :refer [tru]]
@@ -21,6 +22,8 @@
              [db :as db]
              [hydrate :refer [hydrate]]]))
 
+(u/ignore-exceptions (classloader/require 'metabase-enterprise.sandbox.api.util))
+
 (defn- check-self-or-superuser
   "Check that `user-id` is *current-user-id*` or that `*current-user*` is a superuser, or throw a 403."
   [user-id]
@@ -70,7 +73,7 @@
 (api/defendpoint GET "/"
   "Fetch a list of `Users` for the admin People page or for Pulses. By default returns only active users. If
   `include_deactivated` is true, return all Users (active and inactive). (Using `include_deactivated` requires
-  superuser permissions.)"
+  superuser permissions.). For users with segmented permissions, return only themselves."
   [include_deactivated]
   {include_deactivated (s/maybe su/BooleanString)}
   (when include_deactivated
@@ -81,7 +84,10 @@
             (-> {}
                 (hh/merge-order-by [:%lower.last_name :asc] [:%lower.first_name :asc])
                 (hh/merge-where (when-not include_deactivated
-                                  [:= :is_active true]))))
+                                  [:= :is_active true]))
+                (hh/merge-where (when-let [segmented-user? (resolve 'metabase-enterprise.sandbox.api.util/segmented-user?)]
+                                  (when (segmented-user?)
+                                    [:= :id api/*current-user-id*])))))
     ;; For admins, also include the IDs of the  Users' Personal Collections
     api/*is-superuser?* (hydrate :personal_collection_id :group_ids)))
 
diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj
index 6ddacba9c20b3e1a081407a9b1ecbe0ce6494143..5a5e2d7360eecf9392758de62a179cbf5e46963a 100644
--- a/src/metabase/automagic_dashboards/core.clj
+++ b/src/metabase/automagic_dashboards/core.clj
@@ -851,6 +851,7 @@
          :dimensions
          vals
          (mapcat :matches)
+         (filter mi/can-read?)
          filters/interesting-fields
          (map ->related-entity)
          (hash-map :drilldown-fields))))
diff --git a/src/metabase/automagic_dashboards/filters.clj b/src/metabase/automagic_dashboards/filters.clj
index 15263f9cb7b58d0fd9b4e6680bd29eef7ab9a7eb..1365ee067d3ebd33fd0ad95cfbccad139dff541e 100644
--- a/src/metabase/automagic_dashboards/filters.clj
+++ b/src/metabase/automagic_dashboards/filters.clj
@@ -17,7 +17,7 @@
           "head")
    (s/cond-pre s/Int su/KeywordOrString (s/recursive #'FieldReference))])
 
-(def ^:private ^{:arglists '([form])} field-reference?
+(def ^{:arglists '([form])} field-reference?
   "Is given form an MBQL field reference?"
   (complement (s/checker FieldReference)))
 
diff --git a/src/metabase/cmd.clj b/src/metabase/cmd.clj
index 9493e98585c8d85c6dc9acbe7a25c48aac29f6f9..663108b50c1dc9bd012b6dabdf40b97b4875b351 100644
--- a/src/metabase/cmd.clj
+++ b/src/metabase/cmd.clj
@@ -15,12 +15,16 @@
 
   You can see what commands are available by running the command `help`. This command uses the docstrings and arglists
   associated with each command's entrypoint function to generate descriptions for each command."
+  (:refer-clojure :exclude [load])
   (:require [clojure.string :as str]
+            [medley.core :as m]
             [metabase
              [config :as config]
              [db :as mdb]
              [util :as u]]
-            [metabase.plugins.classloader :as classloader]))
+            [metabase.plugins.classloader :as classloader]
+            [metabase.query-processor.util :as qp.util]
+            [metabase.util.i18n :refer [trs]]))
 
 (defn ^:command migrate
   "Run database migrations. Valid options for `direction` are `up`, `force`, `down-one`, `print`, or `release-locks`."
@@ -111,6 +115,40 @@
   (classloader/require 'metabase.cmd.driver-methods)
   ((resolve 'metabase.cmd.driver-methods/print-available-multimethods)))
 
+(defn- cmd-args->map
+  [args]
+  (into {}
+        (for [[k v] (partition 2 args)]
+          [(qp.util/normalize-token (subs k 2)) v])))
+
+(defn- resolve-enterprise-command [symb]
+  (try
+    (classloader/require (symbol (namespace symb)))
+    (resolve symb)
+    (catch Throwable e
+      (throw (ex-info (trs "The ''{0}'' command is only available in Metabase Enterprise Edition." (name symb))
+                      {:command symb}
+                      e)))))
+
+(defn ^:command load
+  "Load serialized metabase instance as created by `dump` command from directory `path`.
+
+   `mode` can be one of `:update` (default) or `:skip`."
+  ([path] (load path :update))
+
+  ([path & args]
+   (let [cmd (resolve-enterprise-command 'metabase-enterprise.serialization.cmd/load)]
+     (cmd path (->> args
+                    cmd-args->map
+                    (m/map-vals qp.util/normalize-token))))))
+
+(defn ^:command dump
+  "Serialized metabase instance into directory `path`."
+  [path & args]
+  (let [cmd (resolve-enterprise-command 'metabase-enterprise.serialization.cmd/dump)
+        {:keys [user]} (cmd-args->map args)]
+    (cmd path user)))
+
 
 ;;; ------------------------------------------------ Running Commands ------------------------------------------------
 
diff --git a/src/metabase/config.clj b/src/metabase/config.clj
index 2cad9267349c7d8ba58f28d269dd324e9f8a32fd..b78315f6db562094e31d39ba050845634e4ddc01 100644
--- a/src/metabase/config.clj
+++ b/src/metabase/config.clj
@@ -32,6 +32,12 @@
    :mb-emoji-in-logs       (str (not is-windows?))                        ; disable them by default when running on Windows. Otherwise they're enabled
    :mb-qp-cache-backend    "db"})
 
+;; separate map for EE stuff so merge conflicts aren't annoying.
+(def ^:private ee-app-defaults
+  {:embed-max-session-age      "1440"   ; how long a FULL APP EMBED session is valid for. One day, by default
+   :mb-session-cookie-samesite "lax"})
+
+(alter-var-root #'app-defaults merge ee-app-defaults)
 
 (defn config-str
   "Retrieve value for a single configuration key.  Accepts either a keyword or a string.
@@ -115,6 +121,17 @@
   local-process-uuid
   (str (UUID/randomUUID)))
 
+(defn- mb-session-cookie-samesite*
+  []
+  (let [same-site (str/lower-case (config-str :mb-session-cookie-samesite))]
+    (when-not (#{"none", "lax", "strict"} same-site)
+      (throw (ex-info "Invalid value for MB_COOKIE_SAMESITE" {:mb-session-cookie-samesite same-site})))
+    (keyword same-site)))
+
+(def ^Keyword mb-session-cookie-samesite
+  "Value for session cookie's `SameSite` directive. Must be one of \"none\", \"lax\", or \"strict\" (case insensitive)."
+  (mb-session-cookie-samesite*))
+
 
 ;; This only affects dev:
 ;;
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 71ee8d94a228d37352570d702ec666ee622fd225..ad8e7e6b8787ee2eb6f3982ab7fed07513701a62 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -23,12 +23,33 @@
             [metabase.util.i18n :refer [deferred-trs trs]]
             [toucan.db :as db]))
 
+(def ^:private ee-available?
+  (try
+    (classloader/require 'metabase-enterprise.core)
+    true
+    (catch Throwable _
+      false)))
+
+;; don't i18n this, it's legalese
+(log/info
+ (format "\nMetabase %s" config/mb-version-string)
+
+ (format "\n\nCopyright © %d Metabase, Inc." (.getYear (java.time.LocalDate/now)))
+
+ (str "\n\n"
+      (if ee-available?
+        (str (deferred-trs "Metabase Enterprise Edition extensions are PRESENT.")
+             "\n\n"
+             (deferred-trs "Usage of Metabase Enterprise Edition features are subject to the Metabase Commercial License.")
+             (deferred-trs "See {0} for details." "https://www.metabase.com/license/commercial/"))
+        (deferred-trs "Metabase Enterprise Edition extensions are NOT PRESENT."))))
+
 ;;; --------------------------------------------------- Lifecycle ----------------------------------------------------
 
 (defn- -init-create-setup-token
   "Create and set a new setup token and log it."
   []
-  (setup/create-token!)                    ; we need this here to create the initial token
+  (setup/create-token!)                 ; we need this here to create the initial token
   (let [hostname  (or (config/config-str :mb-jetty-host) "localhost")
         port      (config/config-int :mb-jetty-port)
         setup-url (str "http://"
diff --git a/src/metabase/db.clj b/src/metabase/db.clj
index 683b2ad98b880b97476461bcb1ada506e2314455..291912e387ca7f00fdbd2a4df768a29ecfb23472 100644
--- a/src/metabase/db.clj
+++ b/src/metabase/db.clj
@@ -101,7 +101,7 @@
   (or (:type @connection-string-details)
       (config/config-kw :mb-db-type)))
 
-(def ^:private db-connection-details
+(def db-connection-details
   "Connection details that can be used when pretending the Metabase DB is itself a `Database` (e.g., to use the Generic
   SQL driver functions on the Metabase DB itself)."
   (delay
diff --git a/src/metabase/driver/mysql.clj b/src/metabase/driver/mysql.clj
index 6b9637344cc2b64ea1f73de092692f73e47d9a53..7a79adc7ee7b9803cba6ec43d2373bf3c1e2bb72 100644
--- a/src/metabase/driver/mysql.clj
+++ b/src/metabase/driver/mysql.clj
@@ -98,6 +98,11 @@
     (recur driver hsql-form (/ amount 1000.0) :second)
     (hsql/call :date_add hsql-form (hsql/raw (format "INTERVAL %s %s" amount (name unit))))))
 
+;; now() returns current timestamp in seconds resolution; now(6) returns it in nanosecond resolution
+(defmethod sql.qp/current-datetime-honeysql-form :mysql
+  [_]
+  (hsql/call :now 6))
+
 (defmethod driver/humanize-connection-error-message :mysql
   [_ message]
   (condp re-matches message
diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj
index 4d6aafd110bc49224d3f7279209e380b36ab1992..a814fd963011b1b075f9d130844fb9e8a7263cd7 100644
--- a/src/metabase/driver/sql/query_processor.clj
+++ b/src/metabase/driver/sql/query_processor.clj
@@ -51,9 +51,11 @@
     (dorun (map hformat/add-anon-param params))
     ;; strip off any trailing semicolons
     (str "(" (str/replace sql #";+\s*$" "") ")"))
+
   PrettyPrintable
   (pretty [_]
     (list 'SQLSourceQuery. sql params))
+
   Object
   (equals [_ other]
     (and (instance? SQLSourceQuery other)
diff --git a/src/metabase/driver/sql_jdbc/connection.clj b/src/metabase/driver/sql_jdbc/connection.clj
index 67b68f8246e06a8e8c7dd43cfd0d6c96a3729f73..12112abaf81d87cd3a5db643c2b5ac26187a129e 100644
--- a/src/metabase/driver/sql_jdbc/connection.clj
+++ b/src/metabase/driver/sql_jdbc/connection.clj
@@ -9,11 +9,21 @@
              [driver :as driver]
              [util :as u]]
             [metabase.models.database :refer [Database]]
+            [metabase.query-processor.error-type :as qp.error-type]
             [metabase.util
-             [i18n :refer [trs]]
+             [i18n :refer [trs tru]]
              [ssh :as ssh]]
             [toucan.db :as db]))
 
+(def ^:deprecated application-db-mock-id
+  "Mock ID used to get a connection to the application DB itself, rather than to a some other data warehouse DB. Only
+  used to make certain driver methods like `metabase.driver/db-default-timezone` work with the application DB
+  itself.
+
+  Try not to use this unless you absolutely have to -- it's only here in the first place because the EE audit code
+  needs to run queries against the application DB itself."
+  -5432)
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                   Interface                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -124,25 +134,43 @@
   "Return a JDBC connection spec that includes a cp30 `ComboPooledDataSource`. These connection pools are cached so we
   don't create multiple ones for the same DB."
   [db-or-id-or-spec]
-  (if (u/id db-or-id-or-spec)
+  (cond
+    ;; db-or-id-or-spec is a Database instance or an integer ID
+    (u/id db-or-id-or-spec)
     (let [database-id (u/get-id db-or-id-or-spec)]
       (or
+       ;; if we're using the special mock ID to refer to the application DB itself, return a connection spec for the
+       ;; application DB
+       (when (= database-id application-db-mock-id)
+         (db/connection))
        ;; we have an existing pool for this database, so use it
        (get @database-id->connection-pool database-id)
        ;; Even tho `set-pool!` will properly shut down old pools if two threads call this method at the same time, we
-       ;; don't want to end up with a bunch of simultaneous threads creating pools only to have them destroyed the very
-       ;; next instant. This will cause their queries to fail. Thus we should do the usual locking here and make sure only
-       ;; one thread will be creating a pool at a given instant.
+       ;; don't want to end up with a bunch of simultaneous threads creating pools only to have them destroyed the
+       ;; very next instant. This will cause their queries to fail. Thus we should do the usual locking here and make
+       ;; sure only one thread will be creating a pool at a given instant.
        (locking database-id->connection-pool
          (or
           ;; check if another thread created the pool while we were waiting to acquire the lock
           (get @database-id->connection-pool database-id)
           ;; create a new pool and add it to our cache, then return it
-          (let [db (db/select-one [Database :id :engine :details] :id database-id)]
+          (let [db (or (db/select-one [Database :id :engine :details] :id database-id)
+                       (throw (ex-info (tru "Database {0} does not exist." database-id)
+                                       {:status-code 404
+                                        :type        qp.error-type/invalid-query
+                                        :database-id database-id})))]
             (u/prog1 (create-pool! db)
               (set-pool! database-id <>)))))))
-    ;; already a spec object
-    db-or-id-or-spec))
+
+    ;; already a `clojure.java.jdbc` spec map
+    (map? db-or-id-or-spec)
+    db-or-id-or-spec
+
+    ;; invalid. Throw Exception
+    :else
+    (throw (ex-info (tru "Not a valid Database/Database ID/JDBC spec")
+                    ;; don't log the actual spec lest we accidentally expose credentials
+                    {:input (class db-or-id-or-spec)}))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/src/metabase/driver/sql_jdbc/execute.clj b/src/metabase/driver/sql_jdbc/execute.clj
index 3cc1539c538b60725a821d9de5458ed0e61d0853..596d5a82547091fe8227545e4a4d60e9dbd9d25e 100644
--- a/src/metabase/driver/sql_jdbc/execute.clj
+++ b/src/metabase/driver/sql_jdbc/execute.clj
@@ -376,20 +376,23 @@
 
 (defn execute-reducible-query
   "Default impl of `execute-reducible-query` for sql-jdbc drivers."
-  {:added "0.35.0", :arglists '([driver query context respond])}
-  [driver {{sql :query, params :params} :native, :as outer-query} context respond]
-  {:pre [(string? sql) (seq sql)]}
-  (let [remark   (qputil/query->remark driver outer-query)
-        sql      (str "-- " remark "\n" sql)
-        max-rows (or (mbql.u/query->max-rows-limit outer-query)
-                     qp.i/absolute-max-results)]
-    (with-open [conn (connection-with-timezone driver (qp.store/database) (qp.timezone/report-timezone-id-if-supported))
-                stmt (doto (prepared-statement* driver conn sql params (context/canceled-chan context))
-                       (.setMaxRows max-rows))
-                rs   (execute-query! driver stmt)]
-      (let [rsmeta           (.getMetaData rs)
-            results-metadata {:cols (column-metadata driver rsmeta)}]
-        (respond results-metadata (reducible-rows driver rs rsmeta (context/canceled-chan context)))))))
+  {:added "0.35.0", :arglists '([driver query context respond] [driver sql params max-rows context respond])}
+  ([driver {{sql :query, params :params} :native, :as outer-query} context respond]
+   {:pre [(string? sql) (seq sql)]}
+   (let [remark   (qputil/query->remark driver outer-query)
+         sql      (str "-- " remark "\n" sql)
+         max-rows (or (mbql.u/query->max-rows-limit outer-query)
+                      qp.i/absolute-max-results)]
+     (execute-reducible-query driver sql params max-rows context respond)))
+
+  ([driver sql params max-rows context respond]
+   (with-open [conn (connection-with-timezone driver (qp.store/database) (qp.timezone/report-timezone-id-if-supported))
+               stmt (doto (prepared-statement* driver conn sql params (context/canceled-chan context))
+                      (.setMaxRows max-rows))
+               rs   (execute-query! driver stmt)]
+     (let [rsmeta           (.getMetaData rs)
+           results-metadata {:cols (column-metadata driver rsmeta)}]
+       (respond results-metadata (reducible-rows driver rs rsmeta (context/canceled-chan context)))))))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                       Convenience Imports from Old Impl                                        |
diff --git a/src/metabase/email.clj b/src/metabase/email.clj
index 4a82fe578b05584309e268dcf6e052bb6a5b1b0a..0dd2b11c1d61b8822210cb3298690e500cc5a9a8 100644
--- a/src/metabase/email.clj
+++ b/src/metabase/email.clj
@@ -84,7 +84,7 @@
         "other types should have a String message.")))
 
 (s/defn send-message-or-throw!
-  "Send an email to one or more RECIPIENTS. Upon success, this returns the MESSAGE that was just sent. This function
+  "Send an email to one or more `recipients`. Upon success, this returns the `message` that was just sent. This function
   does not catch and swallow thrown exceptions, it will bubble up."
   {:style/indent 0}
   [{:keys [subject recipients message-type message]} :- EmailMessage]
@@ -103,8 +103,8 @@
                                :content message}])}))
 
 (defn send-message!
-  "Send an email to one or more RECIPIENTS.
-  RECIPIENTS is a sequence of email addresses; MESSAGE-TYPE must be either `:text` or `:html` or `:attachments`.
+  "Send an email to one or more `recipients`.
+  `recipients` is a sequence of email addresses; `message-type` must be either `:text` or `:html` or `:attachments`.
 
      (email/send-message!
        :subject      \"[Metabase] Password Reset Request\"
@@ -112,7 +112,7 @@
        :message-type :text
        :message      \"How are you today?\")
 
-  Upon success, this returns the MESSAGE that was just sent. This function will catch and log any exception,
+  Upon success, this returns the `message` that was just sent. This function will catch and log any exception,
   returning a map with a description of the error"
   {:style/indent 0}
   [& {:keys [subject recipients message-type message] :as msg-args}]
@@ -120,7 +120,7 @@
     (send-message-or-throw! msg-args)
     (catch Throwable e
       (log/warn e (trs "Failed to send email"))
-      {:error   :ERROR
+      {:error   :ERROR ; Huh?
        :message (.getMessage e)})))
 
 (defn- run-smtp-test
diff --git a/src/metabase/email/_footer.mustache b/src/metabase/email/_footer.mustache
index fffd414890ec1b324f0b6e7429f2aec86637bb54..11a29275d21c491049fe81b2c6d3dbeac4b831a6 100644
--- a/src/metabase/email/_footer.mustache
+++ b/src/metabase/email/_footer.mustache
@@ -4,7 +4,7 @@
   {{/quotation}}
   {{#logoFooter}}
     <div style="padding-bottom: 2em; padding-top: 1em; text-align: center;">
-      <img width="32" height="40" src="http://static.metabase.com/email_logo.png"/>
+      {{> metabase/email/_logo }}
     </div>
   {{/logoFooter}}
 </body>
diff --git a/src/metabase/email/_footer_pulse.mustache b/src/metabase/email/_footer_pulse.mustache
index a0a2e699733f0a2d7b1dd1032c4979d9dee45a55..0e062d0007e97166cd762bbda84f8de4c918518c 100644
--- a/src/metabase/email/_footer_pulse.mustache
+++ b/src/metabase/email/_footer_pulse.mustache
@@ -4,7 +4,7 @@
   {{/quotation}}
   {{#logoFooter}}
     <div style="padding-bottom: 2em; padding-top: 1em; padding-left: 16px; text-align: left;">
-      <img width="32" height="40" src="http://static.metabase.com/email_logo.png"/>
+      {{> metabase/email/_logo }}
     </div>
   {{/logoFooter}}
 </body>
diff --git a/src/metabase/email/_header.mustache b/src/metabase/email/_header.mustache
index 85c926ad42a85155f06ec1d9f7f7e93a55e1297f..30a5e8d4bdd2b7cf6ad855bb0e00938c6e492021 100644
--- a/src/metabase/email/_header.mustache
+++ b/src/metabase/email/_header.mustache
@@ -11,7 +11,7 @@
 <body class="{{emailType}}" style="font-family: 'Helvetica Neue', Helvetica, sans-serif; padding: 1em;">
   {{#logoHeader}}
     <div style="padding-bottom: 2em; padding-top: 1em; text-align: center;">
-      <img width="47" height="60" src="http://static.metabase.com/email_logo.png"/>
+      {{> metabase/email/_logo }}
     </div>
   {{/logoHeader}}
   <div class="container" style="margin: 0; padding: 0 0 2em 0; max-width: 100%; font-size: 16px; line-height: 24px; color: #616D75;">
diff --git a/src/metabase/email/_logo.mustache b/src/metabase/email/_logo.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b76dcd3928374c859ec1e5282ff6a7339f32bc59
--- /dev/null
+++ b/src/metabase/email/_logo.mustache
@@ -0,0 +1,3 @@
+{{#applicationLogoUrl}}
+  <img width="32" height="40" src="{{applicationLogoUrl}}"/>
+{{/applicationLogoUrl}}
\ No newline at end of file
diff --git a/src/metabase/email/follow_up_email.mustache b/src/metabase/email/follow_up_email.mustache
index 06d633b5a7062771796465e5b1fb0355eb88a8c3..6ecc81615da62ad86bd4f8d564282d88140624af 100644
--- a/src/metabase/email/follow_up_email.mustache
+++ b/src/metabase/email/follow_up_email.mustache
@@ -8,7 +8,7 @@
     </p>
     <p>
       <div style="padding: 1em;">
-        <a style="display: inline-block; box-sizing: border-box; text-decoration: none; font-size: 1.063rem; padding: 0.5rem 1.375rem; background: #FBFCFD; border: 1px solid #ddd; color: #444; cursor: pointer; text-decoration: none; font-weight: bold; border-radius: 4px; background-color: #4990E2; border-color: #4990E2; color: #fff;" href="{{link}}">
+        <a style="{{buttonStyle}}" href="{{link}}">
           Take survey
         </a>
       </div>
diff --git a/src/metabase/email/messages.clj b/src/metabase/email/messages.clj
index 039a7322eea7db3f27924ee9d4bcadb687a4b268..5e24bce613b1eaebdb5d0c2e90bdddbf4ca166b3 100644
--- a/src/metabase/email/messages.clj
+++ b/src/metabase/email/messages.clj
@@ -3,6 +3,7 @@
    NOTE: we want to keep this about email formatting, so don't put heavy logic here RE: building data for emails."
   (:require [clojure.core.cache :as cache]
             [clojure.java.io :as io]
+            [clojure.string :as str]
             [clojure.tools.logging :as log]
             [hiccup.core :refer [html]]
             [java-time :as t]
@@ -28,16 +29,65 @@
             [toucan.db :as db])
   (:import [java.io File IOException]))
 
-(when config/is-dev?
-  (alter-meta! #'stencil.core/render-file assoc :style/indent 1))
+(defn- app-name-trs
+  "Return the user configured application name, or Metabase translated
+  via tru if a name isn't configured."
+  []
+  (or (public-settings/application-name)
+      (trs "Metabase")))
 
 ;; Dev only -- disable template caching
 (when config/is-dev?
+  (alter-meta! #'stencil.core/render-file assoc :style/indent 1)
   (stencil-loader/set-cache (cache/ttl-cache-factory {} :ttl 0)))
 
+(def ^:private ^:const data-uri-svg-regex #"^data:image/svg\+xml;base64,(.*)$")
+
+(defn- data-uri-svg? [url]
+  (re-matches data-uri-svg-regex url))
+
+(defn- themed-image-url
+  [url color]
+  (try
+    (let [base64 (second (re-matches data-uri-svg-regex url))
+          svg    (u/decode-base64 base64)
+          themed (str/replace svg #"<svg\b([^>]*)( fill=\"[^\"]*\")([^>]*)>" (str "<svg$1$3 fill=\"" color "\">"))]
+      (str "data:image/svg+xml;base64," (u/encode-base64 themed)))
+  (catch Throwable e
+    url)))
+
+(defn- logo-url []
+  (let [url   (public-settings/application-logo-url)
+        color (public-settings/application-color)]
+    (cond
+      (= url "app/assets/img/logo.svg") "http://static.metabase.com/email_logo.png"
+      ;; NOTE: disabling whitelabeled URLs for now since some email clients don't render them correctly
+      ;; We need to extract them and embed as attachments like we do in metabase.pulse.render.image-bundle
+      true                              nil
+      (data-uri-svg? url)               (themed-image-url url color)
+      :else                             url)))
+
+(defn- button-style [color]
+  (str "display: inline-block; "
+       "box-sizing: border-box; "
+       "padding: 0.5rem 1.375rem; "
+       "font-size: 1.063rem; "
+       "font-weight: bold; "
+       "text-decoration: none; "
+       "cursor: pointer; "
+       "color: #fff; "
+       "border: 1px solid " color "; "
+       "background-color: " color "; "
+       "border-radius: 4px;"))
 
 ;;; Various Context Helper Fns. Used to build Stencil template context
 
+(defn- common-context []
+  {:applicationName    (public-settings/application-name)
+   :applicationColor   (public-settings/application-color)
+   :applicationLogoUrl (logo-url)
+   :buttonStyle        (button-style (public-settings/application-color))})
+
 (defn- random-quote-context []
   (let [data-quote (quotation/random-quote)]
     {:quotation       (:quote data-quote)
@@ -67,7 +117,8 @@
   [invited invitor join-url]
   (let [company      (or (public-settings/site-name) "Unknown")
         message-body (stencil/render-file "metabase/email/new_user_invite"
-                       (merge {:emailType    "new_user_invite"
+                       (merge (common-context)
+                              {:emailType    "new_user_invite"
                                :invitedName  (:first_name invited)
                                :invitorName  (:first_name invitor)
                                :invitorEmail (:email invitor)
@@ -77,7 +128,7 @@
                                :logoHeader   true}
                               (random-quote-context)))]
     (email/send-message!
-      :subject      (str "You're invited to join " company "'s Metabase")
+      :subject      (str (trs "You''re invited to join {0}''s {1}" company (app-name-trs)))
       :recipients   [(:email invited)]
       :message-type :html
       :message      message-body)))
@@ -99,12 +150,13 @@
   (let [recipients (all-admin-recipients)]
     (email/send-message!
       :subject      (str (if google-auth?
-                           (trs "{0} created a Metabase account"     (:common_name new-user))
-                           (trs "{0} accepted their Metabase invite" (:common_name new-user))))
+                           (trs "{0} created a {1} account" (:common_name new-user) (app-name-trs))
+                           (trs "{0} accepted their {1} invite" (:common_name new-user) (app-name-trs))))
       :recipients   recipients
       :message-type :html
       :message      (stencil/render-file "metabase/email/user_joined_notification"
-                      (merge {:logoHeader        true
+                      (merge (common-context)
+                             {:logoHeader        true
                               :joinedUserName    (:first_name new-user)
                               :joinedViaSSO      google-auth?
                               :joinedUserEmail   (:email new-user)
@@ -122,13 +174,14 @@
          (string? hostname)
          (string? password-reset-url)]}
   (let [message-body (stencil/render-file "metabase/email/password_reset"
-                       {:emailType        "password_reset"
-                        :hostname         hostname
-                        :sso              google-auth?
-                        :passwordResetUrl password-reset-url
-                        :logoHeader       true})]
+                       (merge (common-context)
+                              {:emailType        "password_reset"
+                               :hostname         hostname
+                               :sso              google-auth?
+                               :passwordResetUrl password-reset-url
+                               :logoHeader       true}))]
     (email/send-message!
-      :subject      (trs "[Metabase] Password Reset Request")
+      :subject      (trs "[{0}] Password Reset Request" (app-name-trs))
       :recipients   [email]
       :message-type :html
       :message      message-body)))
@@ -165,9 +218,10 @@
   (let [context      (merge (update context :dependencies build-dependencies)
                             notification-context
                             (random-quote-context))
-        message-body (stencil/render-file "metabase/email/notification" context)]
+        message-body (stencil/render-file "metabase/email/notification"
+                                          (merge (common-context) context))]
     (email/send-message!
-      :subject      (trs "[Metabase] Notification")
+      :subject      (trs "[{0}] Notification" (app-name-trs))
       :recipients   [email]
       :message-type :html
       :message      message-body)))
@@ -177,14 +231,15 @@
   [email msg-type]
   {:pre [(u/email? email) (contains? #{"abandon" "follow-up"} msg-type)]}
   (let [subject      (str (if (= "abandon" msg-type)
-                            (trs "[Metabase] Help make Metabase better.")
-                            (trs "[Metabase] Tell us how things are going.")))
+                            (trs "[{0}] Help make [{1}] better." (app-name-trs) (app-name-trs))
+                            (trs "[{0}] Tell us how things are going." (app-name-trs))))
         context      (merge notification-context
                             (random-quote-context)
                             (if (= "abandon" msg-type)
                               (abandonment-context)
                               (follow-up-context)))
-        message-body (stencil/render-file "metabase/email/follow_up_email" context)]
+        message-body (stencil/render-file "metabase/email/follow_up_email"
+                                          (merge (common-context) context))]
     (email/send-message!
       :subject      subject
       :recipients   [email]
@@ -198,7 +253,8 @@
    :content      url})
 
 (defn- pulse-context [pulse]
-  (merge {:emailType    "pulse"
+  (merge (common-context)
+         {:emailType    "pulse"
           :pulseName    (:name pulse)
           :sectionStyle (render.style/style (render.style/section-style))
           :colorGrey4   render.style/color-gray-4
diff --git a/src/metabase/email/new_user_invite.mustache b/src/metabase/email/new_user_invite.mustache
index 0480b1161106f9d523639b97e5f25861293b6397..d759a00991241d0e9b3d6bb50f1f96cf34f1134b 100644
--- a/src/metabase/email/new_user_invite.mustache
+++ b/src/metabase/email/new_user_invite.mustache
@@ -1,7 +1,7 @@
 {{> metabase/email/_header }}
   <div style="text-align: center;">
     <div style="padding-bottom: 1em;">
-      <h2 style="font-weight: normal; color: #4C545B; line-height: 2rem;">{{invitorName}} wants you to join them on Metabase</h2>
+      <h2 style="font-weight: normal; color: #4C545B; line-height: 2rem;">{{invitorName}} wants you to join them on {{applicationName}}</h2>
     </div>
     <div style="padding: 0.25em 0em .25em 0em; text-align: center; margin-left: auto; margin-right: auto; max-width: 400px; position: relative;">
       <table width="296" height="141" cellpadding="0" cellspacing="0" style="display:block;margin:0 auto;">
@@ -15,7 +15,7 @@
       <p style="line-height: 1.3rem; font-size: small">{{invitedName}}'s Happiness and Productivity Over&nbsp;Time</p>
     </div>
     <div style="max-width: 450px; margin-left: auto; margin-right: auto; line-height: 1.2rem;">
-      <p>Metabase is a simple and powerful analytics tool which lets <b>anyone</b> learn and <b>make decisions</b> from their company's data.</p>
+      <p>{{applicationName}} is a simple and powerful analytics tool which lets <b>anyone</b> learn and <b>make decisions</b> from their company's data.</p>
       <p>No technical knowledge required!</p>
     </div>
     <div style="padding: 1em 0 1em 0;">
diff --git a/src/metabase/email/password_reset.mustache b/src/metabase/email/password_reset.mustache
index d948cfecbeeb3ec0ffbe233296f16eedd01b0622..9116a1024f87786ac43f6c3abdd6ed1bab2d4504 100644
--- a/src/metabase/email/password_reset.mustache
+++ b/src/metabase/email/password_reset.mustache
@@ -1,12 +1,12 @@
 {{> metabase/email/_header }}
   <div>
     {{#sso}}
-      <p>You're using Google to log in to Metabase, so you don't have a password. You can log in to Metabase by clicking "Sign in with Google"</p>
-      <a href="{{hostname}}">Go to Metabase</a>
+      <p>You're using Google to log in to {{applicationName}}, so you don't have a password. You can log in to {{applicationName}} by clicking "Sign in with Google"</p>
+      <a href="{{hostname}}">Go to {{applicationName}}</a>
     {{/sso}}
     {{^sso}}
     <div style="text-align: center">
-      <p>Click the button below to reset the password for your Metabase account at {{hostname}}.</p>
+      <p>Click the button below to reset the password for your {{applicationName}} account at {{hostname}}.</p>
       <a style="display: inline-block; box-sizing: border-box; text-decoration: none; font-size: 1.063rem; padding: 0.5rem 1.375rem; background: #FBFCFD; border: 1px solid #ddd; color: #444; cursor: pointer; text-decoration: none; border-radius: 4px; background-color: #4990E2; border-color: #4990E2; color: #fff;" href="{{passwordResetUrl}}">Reset password</a>
       <p style="padding-top: 2em; font-size: small;">Didn't request this password reset? It's safe to ignore it.</p>
     </div>
diff --git a/src/metabase/email/user_joined_notification.mustache b/src/metabase/email/user_joined_notification.mustache
index 25f5ca52076ad185a8efc3d8496716e19deca158..60ac280d8b1a64a4ef2f7b7b1861b51394f89382 100644
--- a/src/metabase/email/user_joined_notification.mustache
+++ b/src/metabase/email/user_joined_notification.mustache
@@ -3,18 +3,18 @@
     <div style="padding-bottom: 1em;">
       <h2 style="font-weight: normal; color: #4C545B;line-height: 1.65rem;">
         {{joinedUserName}}
-        {{#joinedViaSSO}}created a Metabase account.{{/joinedViaSSO}}
-        {{^joinedViaSSO}}accepted their Metabase invitation.{{/joinedViaSSO}}
+        {{#joinedViaSSO}}created a {{applicationName}} account.{{/joinedViaSSO}}
+        {{^joinedViaSSO}}accepted their {{applicationName}} invitation.{{/joinedViaSSO}}
       </h2>
       <h4 style="font-weight: normal;">
         <a style="color: #4A90E2; text-decoration: none;" href="mailto:{{adminEmail}}">
           {{joinedUserEmail}}
         </a>
-        joined {{#joinedViaSSO}}via Google Auth{{/joinedViaSSO}} on {{joinedDate}}
+        joined {{#joinedViaSSO}}via Single Sign-On{{/joinedViaSSO}} on {{joinedDate}}
       </h4>
     </div>
     <div style="padding: 1em;">
-      <a style="display: inline-block; box-sizing: border-box; text-decoration: none; font-size: 1.063rem; padding: 0.5rem 1.375rem; background: #FBFCFD; border: 1px solid #ddd; color: #444; cursor: pointer; text-decoration: none; font-weight: bold; border-radius: 4px; background-color: #4990E2; border-color: #4990E2; color: #fff;" href="{{joinedUserEditUrl}}">
+      <a style="{{buttonStyle}}" href="{{joinedUserEditUrl}}">
         Edit {{joinedUserName}}'s settings
       </a>
     </div>
diff --git a/src/metabase/handler.clj b/src/metabase/handler.clj
index a4ca4e5e3c9fa07acaf8b361783b42e628cda394..af8ccd0643d1d11acfce4d9dd9ce6ad886a3e272 100644
--- a/src/metabase/handler.clj
+++ b/src/metabase/handler.clj
@@ -51,5 +51,6 @@
    mw.misc/add-content-type                ; Adds a Content-Type header for any response that doesn't already have one
    mw.misc/disable-streaming-buffering     ; Add header to streaming (async) responses so ngnix doesn't buffer keepalive bytes
    wrap-gzip                               ; GZIP response if client can handle it
+   mw.misc/bind-request                    ; bind `metabase.middleware.misc/*request*` for the duration of the request
    mw.ssl/redirect-to-https-middleware))   ; Redirect to HTTPS if configured to do so
 ;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP
diff --git a/src/metabase/integrations/ldap.clj b/src/metabase/integrations/ldap.clj
index 19544324ba51b1513ceb9f7b23b87e0301594d2d..c66a75f313d10997a3817ad3fae36142a36e171b 100644
--- a/src/metabase/integrations/ldap.clj
+++ b/src/metabase/integrations/ldap.clj
@@ -1,18 +1,20 @@
 (ns metabase.integrations.ldap
   (:require [cheshire.core :as json]
             [clj-ldap.client :as ldap]
-            [clojure.string :as str]
-            [metabase.integrations.common :as integrations.common]
+            [clojure.tools.logging :as log]
+            [metabase.integrations.ldap
+             [default-implementation :as default-impl]
+             [interface :as i]]
             [metabase.models
              [setting :as setting :refer [defsetting]]
-             [user :as user :refer [User]]]
+             [user :refer [User]]]
+            [metabase.plugins.classloader :as classloader]
             [metabase.util :as u]
-            [metabase.util.i18n :refer [deferred-tru tru]]
-            [toucan.db :as db])
-  (:import [com.unboundid.ldap.sdk DN Filter LDAPConnectionPool LDAPException]))
-
-(def ^:private filter-placeholder
-  "{login}")
+            [metabase.util
+             [i18n :refer [deferred-tru tru]]
+             [schema :as su]]
+            [schema.core :as s])
+  (:import [com.unboundid.ldap.sdk DN LDAPConnectionPool LDAPException]))
 
 (defsetting ldap-enabled
   (deferred-tru "Enable LDAP authentication.")
@@ -85,6 +87,17 @@
                  (throw (IllegalArgumentException. (tru "{0} is not a valid DN." (name k))))))
              (setting/set-json! :ldap-group-mappings new-value)))
 
+(defsetting ldap-sync-user-attributes
+  (deferred-tru "Should we sync user attributes when someone logs in via LDAP?")
+  :type :boolean
+  :default true)
+
+;; TODO - maybe we want to add a csv setting type?
+(defsetting ldap-sync-user-attributes-blacklist
+  (deferred-tru "Comma-separated list of user attributes to skip syncing for LDAP users.")
+  :default "userPassword,dn,distinguishedName"
+  :type    :csv)
+
 (defsetting ldap-configured?
   "Check if LDAP is enabled and that the mandatory settings are configured."
   :type       :boolean
@@ -118,30 +131,17 @@
   ^LDAPConnectionPool []
   (ldap/connect (settings->ldap-options)))
 
-(defn- with-connection
-  "Applies `f` with a connection and `args`"
-  [f & args]
+(defn- do-with-ldap-connection
+  "Impl for `with-ldap-connection` macro."
+  [f]
   (with-open [conn (get-connection)]
-    (apply f conn args)))
-
-(defn- ldap-groups->mb-group-ids
-  "Will translate a set of DNs to a set of MB group IDs using the configured mappings."
-  [ldap-groups]
-  (-> (ldap-group-mappings)
-      (select-keys (map #(DN. (str %)) ldap-groups))
-      vals
-      flatten
-      set))
-
-(defn- get-user-groups
-  "Retrieve groups for a supplied DN."
-  ([^String dn]
-    (with-connection get-user-groups dn))
-  ([conn ^String dn]
-    (when (ldap-group-base)
-      (let [results (ldap/search conn (ldap-group-base) {:scope  :sub
-                                                         :filter (Filter/createEqualityFilter "member" dn)})]
-        (map :dn results)))))
+    (f conn)))
+
+(defmacro ^:private with-ldap-connection
+  "Execute `body` with `connection-binding` bound to a LDAP connection."
+  [[connection-binding] & body]
+  `(do-with-ldap-connection (fn [~(vary-meta connection-binding assoc :tag `LDAPConnectionPool)]
+                              ~@body)))
 
 (def ^:private user-base-error  {:status :ERROR, :message "User search base does not exist or is unreadable"})
 (def ^:private group-base-error {:status :ERROR, :message "Group search base does not exist or is unreadable"})
@@ -179,54 +179,49 @@
     (catch Exception e
       {:status :ERROR, :message (.getMessage e)})))
 
-(defn- search [conn, ^String username]
-  (first
-   (ldap/search
-    conn
-    (ldap-user-base)
-    {:scope      :sub
-     :filter     (str/replace (ldap-user-filter) filter-placeholder (Filter/encodeValue username))
-     :size-limit 1})))
-
-(defn find-user
-  "Gets user information for the supplied username."
-  ([username]
-   (with-connection find-user username))
-
-  ([conn username]
-   (when-let [{:keys [dn], :as result} (u/lower-case-map-keys (search conn username))]
-     (let [{fname (keyword (ldap-attribute-firstname))
-            lname (keyword (ldap-attribute-lastname))
-            email (keyword (ldap-attribute-email))}    result]
-       ;; Make sure we got everything as these are all required for new accounts
-       (when-not (some empty? [dn fname lname email])
-         {:dn         dn
-          :first-name fname
-          :last-name  lname
-          :email      email
-          :groups     (when (ldap-group-sync)
-                        ;; Active Directory and others (like FreeIPA) will supply a `memberOf` overlay attribute for
-                        ;; groups. Otherwise we have to make the inverse query to get them.
-                        (or (:memberof result) (get-user-groups dn) []))})))))
-
 (defn verify-password
   "Verifies if the supplied password is valid for the `user-info` (from `find-user`) or DN."
   ([user-info password]
-   (with-connection verify-password user-info password))
+   (with-ldap-connection [conn]
+     (verify-password conn user-info password)))
 
   ([conn user-info password]
    (let [dn (if (string? user-info) user-info (:dn user-info))]
      (ldap/bind? conn dn password))))
 
-(defn fetch-or-create-user!
+;; we want the EE implementation namespace to be loaded immediately if present so the extra Settings that it defines
+;; are available elsewhere (e.g. so they'll show up in the API endpoints that list Settings)
+(def ^:private impl
+  ;; if EE impl is present, use it. It implements the strategy pattern and will forward method invocations to the
+  ;; default OSS impl if we don't have a valid EE token. Thus the actual EE versions of the methods won't get used
+  ;; unless EE code is present *and* we have a valid EE token.
+  (u/prog1 (or (u/ignore-exceptions
+                 (classloader/require 'metabase-enterprise.enhancements.integrations.ldap)
+                 (some-> (resolve 'metabase-enterprise.enhancements.integrations.ldap/ee-strategy-impl) var-get))
+               default-impl/impl)
+    (log/debugf "LDAP integration set to %s" <>)))
+
+(s/defn ^:private ldap-settings :- i/LDAPSettings
+  []
+  {:first-name-attribute (ldap-attribute-firstname)
+   :last-name-attribute  (ldap-attribute-lastname)
+   :email-attribute      (ldap-attribute-email)
+   :sync-groups?         (ldap-group-sync)
+   :group-base           (ldap-group-base)
+   :group-mappings       (ldap-group-mappings)
+   :user-base            (ldap-user-base)
+   :user-filter          (ldap-user-filter)})
+
+(s/defn find-user :- (s/maybe i/UserInfo)
+  "Get user information for the supplied username."
+  ([username :- su/NonBlankString]
+   (with-ldap-connection [conn]
+     (find-user conn username)))
+
+  ([ldap-connection :- LDAPConnectionPool, username :- su/NonBlankString]
+   (i/find-user impl ldap-connection username (ldap-settings))))
+
+(s/defn fetch-or-create-user! :- (class User)
   "Using the `user-info` (from `find-user`) get the corresponding Metabase user, creating it if necessary."
-  [{:keys [first-name last-name email groups]}]
-  (let [user (or (db/select-one [User :id :last_login] :email email)
-                 (user/create-new-ldap-auth-user!
-                  {:first_name first-name
-                   :last_name  last-name
-                   :email      email}))]
-    (u/prog1 user
-      (when (ldap-group-sync)
-        (let [group-ids (ldap-groups->mb-group-ids groups)]
-          (integrations.common/sync-group-memberships! user group-ids))))))
+  [user-info :- i/UserInfo]
+  (i/fetch-or-create-user! impl user-info (ldap-settings)))
diff --git a/src/metabase/integrations/ldap/default_implementation.clj b/src/metabase/integrations/ldap/default_implementation.clj
new file mode 100644
index 0000000000000000000000000000000000000000..d40f4deed47f7049148d01eb767b25d42b375d91
--- /dev/null
+++ b/src/metabase/integrations/ldap/default_implementation.clj
@@ -0,0 +1,122 @@
+(ns metabase.integrations.ldap.default-implementation
+  "Default LDAP integration. This integration is used by OSS or for EE if enterprise features are not enabled."
+  (:require [clj-ldap.client :as ldap-client]
+            [clojure.string :as str]
+            [metabase.integrations.common :as integrations.common]
+            [metabase.integrations.ldap.interface :as i]
+            [metabase.models.user :as user :refer [User]]
+            [metabase.util :as u]
+            [metabase.util.schema :as su]
+            [pretty.core :refer [PrettyPrintable]]
+            [schema.core :as s]
+            [toucan.db :as db])
+  (:import [com.unboundid.ldap.sdk DN Filter LDAPConnectionPool]
+           metabase.integrations.ldap.interface.LDAPIntegration))
+
+;;; --------------------------------------------------- find-user ----------------------------------------------------
+
+(def ^:private filter-placeholder
+  "{login}")
+
+(s/defn search :- (s/maybe su/Map)
+  "Search for a LDAP user with `username`."
+  [ldap-connection :- LDAPConnectionPool
+   username        :- su/NonBlankString
+   {:keys [user-base
+           user-filter]} :- i/LDAPSettings]
+  (some-> (first
+           (ldap-client/search
+            ldap-connection
+            user-base
+            {:scope      :sub
+             :filter     (str/replace user-filter filter-placeholder (Filter/encodeValue ^String username))
+             :size-limit 1}))
+          u/lower-case-map-keys))
+
+(s/defn ^:private user-groups :- (s/maybe [su/NonBlankString])
+  "Retrieve groups for a supplied DN."
+  [ldap-connection :- LDAPConnectionPool
+   dn              :- su/NonBlankString
+   {:keys [group-base]} :- i/LDAPSettings]
+  (when group-base
+    (let [results (ldap-client/search
+                   ldap-connection
+                   group-base
+                   {:scope  :sub
+                    :filter (Filter/createEqualityFilter "member" ^String dn)})]
+      (map :dn results))))
+
+(s/defn ldap-search-result->user-info :- (s/maybe i/UserInfo)
+  "Convert the result "
+  [ldap-connection          :- LDAPConnectionPool
+   {:keys [dn], :as result} :- su/Map
+   {:keys [first-name-attribute
+           last-name-attribute
+           email-attribute
+           sync-groups?]
+    :as   settings} :- i/LDAPSettings]
+  (let [{first-name (keyword first-name-attribute)
+         last-name  (keyword last-name-attribute)
+         email      (keyword email-attribute)} result]
+    ;; Make sure we got everything as these are all required for new accounts
+    (when-not (some empty? [dn first-name last-name email])
+      {:dn         dn
+       :first-name first-name
+       :last-name  last-name
+       :email      email
+       :groups     (when sync-groups?
+                     ;; Active Directory and others (like FreeIPA) will supply a `memberOf` overlay attribute for
+                     ;; groups. Otherwise we have to make the inverse query to get them.
+                     (or (:memberof result)
+                         (user-groups ldap-connection dn settings)
+                         []))})))
+
+(s/defn ^:private find-user* :- (s/maybe i/UserInfo)
+  [ldap-connection :- LDAPConnectionPool
+   username        :- su/NonBlankString
+   settings        :- i/LDAPSettings]
+  (when-let [result (search ldap-connection username settings)]
+    (ldap-search-result->user-info ldap-connection result settings)))
+
+
+;;; --------------------------------------------- fetch-or-create-user! ----------------------------------------------
+
+(s/defn ldap-groups->mb-group-ids :- #{su/IntGreaterThanZero}
+  "Translate a set of DNs to a set of MB group IDs using the configured mappings."
+  [ldap-groups              :- (s/maybe [su/NonBlankString])
+   {:keys [group-mappings]} :- (select-keys i/LDAPSettings [:group-mappings s/Keyword])]
+  (-> group-mappings
+      (select-keys (map #(DN. (str %)) ldap-groups))
+      vals
+      flatten
+      set))
+
+(s/defn ^:private fetch-or-create-user!* :- (class User)
+  [{:keys [first-name last-name email groups]} :- i/UserInfo
+   {:keys [sync-groups?], :as settings}        :- i/LDAPSettings]
+  (let [user (or (db/select-one [User :id :last_login] :email email)
+                 (user/create-new-ldap-auth-user!
+                  {:first_name first-name
+                   :last_name  last-name
+                   :email      email}))]
+    (u/prog1 user
+      (when sync-groups?
+        (let [group-ids (ldap-groups->mb-group-ids groups settings)]
+          (integrations.common/sync-group-memberships! user group-ids))))))
+
+
+;;; ------------------------------------------------------ impl ------------------------------------------------------
+
+(def impl
+  "Default LDAP integration."
+  (reify
+    PrettyPrintable
+    (pretty [_]
+      `impl)
+
+    LDAPIntegration
+    (find-user [_ ldap-connection username ldap-settings]
+      (find-user* ldap-connection username ldap-settings))
+
+    (fetch-or-create-user! [_ user-info ldap-settings]
+      (fetch-or-create-user!* user-info ldap-settings))))
diff --git a/src/metabase/integrations/ldap/interface.clj b/src/metabase/integrations/ldap/interface.clj
new file mode 100644
index 0000000000000000000000000000000000000000..535c0b5ab2902ff48e4502756c22efb4ac3b0925
--- /dev/null
+++ b/src/metabase/integrations/ldap/interface.clj
@@ -0,0 +1,40 @@
+(ns metabase.integrations.ldap.interface
+  "There are separate EE and OSS versions of the LDAP integration; this namespace defines a common protocol both
+  implementations conform to."
+  (:require [metabase.util.schema :as su]
+            [potemkin :as p]
+            [schema.core :as s])
+  (:import com.unboundid.ldap.sdk.DN))
+
+(def UserInfo
+  "Schema for LDAP User info as returned by `user-info` and used as input to `fetch-or-create-user!`."
+  {:dn         su/NonBlankString
+   :first-name su/NonBlankString
+   :last-name  su/NonBlankString
+   :email      su/Email
+   :groups     [su/NonBlankString]
+   s/Keyword   s/Any})
+
+(def LDAPSettings
+  "Options passed to LDAP integration implementations. These are just the various LDAP Settings from
+  `metabase.integrations.ldap`, packaged up as a single map so implementations don't need to fetch Setting values
+  directly."
+  {:first-name-attribute su/NonBlankString
+   :last-name-attribute  su/NonBlankString
+   :email-attribute      su/NonBlankString
+   :sync-groups?         s/Bool
+   :group-base           (s/maybe su/NonBlankString)
+   :group-mappings       (s/maybe {DN [su/IntGreaterThanZero]})
+   :user-base            su/NonBlankString
+   :user-filter          su/NonBlankString
+   s/Keyword             s/Any})
+
+(p/defprotocol+ LDAPIntegration
+  "Protocol for LDAP integration implementations."
+  (find-user [this ^com.unboundid.ldap.sdk.LDAPConnectionPool ldap-connection username ldap-settings]
+    "Find LDAP user with `username`. If a corresponding LDAP user is found, result should be in the format specified
+  by the `UserInfo` schema above. `ldap-settings` match the `LDAPSettings` schema above.")
+
+  (fetch-or-create-user! [this user-info ldap-settings]
+    "Using the `user-info` (from `find-user`) get the corresponding Metabase user, creating it if necessary.
+    `ldap-settings` match the `LDAPSettings` schema above."))
diff --git a/src/metabase/metabot/command.clj b/src/metabase/metabot/command.clj
index 3c2d283c5d3ba53df6385ae2288b3bc11955e588..82428e16b2dae7fd88194dd3a9e4271d511e259b 100644
--- a/src/metabase/metabot/command.clj
+++ b/src/metabase/metabot/command.clj
@@ -166,12 +166,12 @@
      (when-not card-id
        (throw (Exception. (tru "Card {0} not found." card-id-or-name))))
      (with-metabot-permissions
-       (read-check Card card-id))
-     (metabot.slack/async
-       (let [attachments (pulse/create-and-upload-slack-attachments!
-                          (pulse/create-slack-attachment-data
-                           [(pulse/execute-card {} card-id, :context :metabot)]))]
-         (metabot.slack/post-chat-message! nil attachments)))
+       (read-check Card card-id)
+       (metabot.slack/async
+         (let [attachments (pulse/create-and-upload-slack-attachments!
+                            (pulse/create-slack-attachment-data
+                             [(pulse/execute-card {} card-id, :context :metabot)]))]
+           (metabot.slack/post-chat-message! nil attachments))))
      (tru "Ok, just a second...")))
 
   ;; If the card name comes without spaces, e.g. (show 'my 'wacky 'card) turn it into a string an recur: (show "my
diff --git a/src/metabase/middleware/misc.clj b/src/metabase/middleware/misc.clj
index b74b23cf97ee1bd865d152efb33354071e7a3559..e66e327b8906055c8fc276069052a26e4fc0ae92 100644
--- a/src/metabase/middleware/misc.clj
+++ b/src/metabase/middleware/misc.clj
@@ -77,3 +77,17 @@
      request
      (comp respond maybe-add-disable-buffering-header)
      raise)))
+
+
+;;; -------------------------------------------------- Bind request --------------------------------------------------
+
+(def ^:dynamic *request*
+  "The Ring request currently being handled by this thread, if any."
+  nil)
+
+(defn bind-request
+  "Ring middleware that binds `*request*` for the duration of this Ring request."
+  [handler]
+  (fn [request respond raise]
+    (binding [*request* request]
+      (handler request respond raise))))
diff --git a/src/metabase/middleware/security.clj b/src/metabase/middleware/security.clj
index 7c3d73c2909126d66290a52d667beecf15f57be4..55cc96285609033ac40997819ce4a0ea5119f8c8 100644
--- a/src/metabase/middleware/security.clj
+++ b/src/metabase/middleware/security.clj
@@ -3,7 +3,9 @@
   (:require [clojure.java.io :as io]
             [clojure.string :as str]
             [java-time :as t]
-            [metabase.config :as config]
+            [metabase
+             [config :as config]
+             [public-settings :as public-settings]]
             [metabase.middleware.util :as middleware.u]
             [metabase.models.setting :refer [defsetting]]
             [metabase.util.i18n :as ui18n :refer [deferred-tru]]
@@ -83,12 +85,30 @@
                   :manifest-src ["'self'"]}]
       (format "%s %s; " (name k) (str/join " " vs))))})
 
+(defn- embedding-app-origin
+  []
+  (when (and (public-settings/enable-embedding) (public-settings/embedding-app-origin))
+    (public-settings/embedding-app-origin)))
+
+(defn- content-security-policy-header-with-frame-ancestors
+  [allow-iframes?]
+  (update content-security-policy-header
+          "Content-Security-Policy"
+          #(format "%s frame-ancestors %s;" % (if allow-iframes? "*" (or (embedding-app-origin) "'none'")))))
+
 (defsetting ssl-certificate-public-key
   (str (deferred-tru "Base-64 encoded public key for this site's SSL certificate.")
        (deferred-tru "Specify this to enable HTTP Public Key Pinning.")
        (deferred-tru "See {0} for more information." "http://mzl.la/1EnfqBf")))
 ;; TODO - it would be nice if we could make this a proper link in the UI; consider enabling markdown parsing
 
+(defn- first-embedding-app-origin
+  "Return only the first embedding app origin."
+  []
+  (some-> (embedding-app-origin)
+          (str/split #" ")
+          first))
+
 (defn security-headers
   "Fetch a map of security headers that should be added to a response based on the passed options."
   [& {:keys [allow-iframes? allow-cache?]
@@ -98,10 +118,12 @@
      (cache-far-future-headers)
      (cache-prevention-headers))
    strict-transport-security-header
-   content-security-policy-header
+   (content-security-policy-header-with-frame-ancestors allow-iframes?)
    (when-not allow-iframes?
      ;; Tell browsers not to render our site as an iframe (prevent clickjacking)
-     {"X-Frame-Options"                 "DENY"})
+     {"X-Frame-Options"                 (if (embedding-app-origin)
+                                          (format "ALLOW-FROM %s" (first-embedding-app-origin))
+                                          "DENY")})
    { ;; Tell browser to block suspected XSS attacks
     "X-XSS-Protection"                  "1; mode=block"
     ;; Prevent Flash / PDF files from including content from site.
diff --git a/src/metabase/middleware/session.clj b/src/metabase/middleware/session.clj
index 9fc0f44ae28b00463d82bf04fa97be276189b41b..af638ab92bb72fd60aa4bdcfaa136b84cd0ecfe3 100644
--- a/src/metabase/middleware/session.clj
+++ b/src/metabase/middleware/session.clj
@@ -6,14 +6,15 @@
             [honeysql.core :as hsql]
             [metabase
              [config :as config]
-             [db :as mdb]]
+             [db :as mdb]
+             [util :as u]]
             [metabase.api.common :refer [*current-user* *current-user-id* *current-user-permissions-set* *is-superuser?*]]
             [metabase.core.initialization-status :as init-status]
             [metabase.driver.sql.query-processor :as sql.qp]
             [metabase.models
              [session :refer [Session]]
              [user :as user :refer [User]]]
-            [metabase.util.i18n :as i18n]
+            [metabase.util.i18n :as i18n :refer [deferred-trs tru]]
             [ring.util.response :as resp]
             [schema.core :as s]
             [toucan.db :as db])
@@ -31,9 +32,10 @@
 ;;
 ;; Finally we'll check for the presence of a `X-Metabase-Session` header. If that isn't present, you don't have a
 ;; Session ID and thus are definitely not authenticated
-(def ^:private ^String metabase-session-cookie        "metabase.SESSION")
-(def ^:private ^String metabase-legacy-session-cookie "metabase.SESSION_ID") ; this can be removed in 0.33.x
-(def ^:private ^String metabase-session-header        "x-metabase-session")
+
+(def ^:private ^String metabase-session-cookie          "metabase.SESSION")
+(def ^:private ^String metabase-embedded-session-cookie "metabase.EMBEDDED_SESSION")
+(def ^:private ^String anti-csrf-token-header           "x-metabase-anti-csrf-token")
 
 (defn- clear-cookie [response cookie-name]
   (resp/set-cookie response cookie-name nil {:expires "Thu, 1 Jan 1970 00:00:00 GMT", :path "/"}))
@@ -75,63 +77,123 @@
     scheme
     (= scheme :https)))
 
-(s/defn set-session-cookie
-  "Add a `Set-Cookie` header to `response` to persist the Metabase session."
-  [request response, session-id :- UUID]
-  (-> response
-      wrap-body-if-needed
-      (clear-cookie metabase-legacy-session-cookie)
-      ;; See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie and `ring.middleware.cookies`
-      (resp/set-cookie
-       metabase-session-cookie
-       (str session-id)
-       (merge
-        {:same-site :lax
-         :http-only true
-         :path      "/"}
-        ;; If the env var `MB_SESSION_COOKIES=true`, do not set the `Max-Age` directive; cookies with no `Max-Age` and
-        ;; no `Expires` directives are session cookies, and are deleted when the browser is closed
-        ;;
-        ;; See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Session_cookies
-        (when-not (config/config-bool :mb-session-cookies)
-          ;; max-session age-is in minutes; Max-Age= directive should be in seconds
-          {:max-age (* 60 (config/config-int :max-session-age))})
-        ;; If the authentication request request was made over HTTPS (hopefully always except for local dev instances)
-        ;; add `Secure` attribute so the cookie is only sent over HTTPS.
-        (when (https-request? request)
-          {:secure true})))))
-
 (defn clear-session-cookie
   "Add a header to `response` to clear the current Metabase session cookie."
   [response]
-  (-> response
-      wrap-body-if-needed
-      (clear-cookie metabase-session-cookie)
-      (clear-cookie metabase-legacy-session-cookie)))
-
-(defn- wrap-session-id* [{:keys [cookies headers] :as request}]
-  (let [session-id (or (get-in cookies [metabase-session-cookie :value])
-                       (get-in cookies [metabase-legacy-session-cookie :value])
-                       (headers metabase-session-header))]
-    (if (seq session-id)
-      (assoc request :metabase-session-id session-id)
-      request)))
+  (reduce clear-cookie (wrap-body-if-needed response) [metabase-session-cookie metabase-embedded-session-cookie]))
+
+(defmulti set-session-cookie
+  "Add an appropriate cookie to persist a newly created Session to `response`."
+  {:arglists '([request response session])}
+  (fn [_ _ {session-type :type}] (keyword session-type)))
+
+(defmethod set-session-cookie :default
+  [_ _ session]
+  (throw (ex-info (str (tru "Invalid session. Expected an instance of Session."))
+           {:session session})))
+
+(s/defmethod set-session-cookie :normal
+  [request response {session-uuid :id} :- {:id (s/cond-pre UUID u/uuid-regex), s/Keyword s/Any}]
+  (let [response       (wrap-body-if-needed response)
+        cookie-options (merge
+                        {:same-site config/mb-session-cookie-samesite
+                         :http-only true
+                         ;; TODO - we should set `site-path` as well. Don't want to enable this yet so we don't end
+                         ;; up breaking things
+                         :path      "/" #_ (site-path)}
+                        ;; If the env var `MB_SESSION_COOKIES=true`, do not set the `Max-Age` directive; cookies
+                        ;; with no `Max-Age` and no `Expires` directives are session cookies, and are deleted when
+                        ;; the browser is closed
+                        ;;
+                        ;; See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Session_cookies
+                        (when-not (config/config-bool :mb-session-cookies)
+                          ;; max-session age-is in minutes; Max-Age= directive should be in seconds
+                          {:max-age (* 60 (config/config-int :max-session-age))})
+                        ;; If the authentication request request was made over HTTPS (hopefully always except for
+                        ;; local dev instances) add `Secure` attribute so the cookie is only sent over HTTPS.
+                        (when (https-request? request)
+                          {:secure true})
+                        (when (= config/mb-session-cookie-samesite :none)
+                          (log/warn
+                           (str (deferred-trs "Session cookie's SameSite is configured to \"None\", but site is")
+                                (deferred-trs "served over an insecure connection. Some browsers will reject ")
+                                (deferred-trs "cookies under these conditions. ")
+                                (deferred-trs "https://www.chromestatus.com/feature/5633521622188032")))))]
+    (resp/set-cookie response metabase-session-cookie (str session-uuid) cookie-options)))
+
+(s/defmethod set-session-cookie :full-app-embed
+  [request response {session-uuid :id, anti-csrf-token :anti_csrf_token} :- {:id       (s/cond-pre UUID u/uuid-regex)
+                                                                             s/Keyword s/Any}]
+  (let [response       (wrap-body-if-needed response)
+        cookie-options (merge
+                        {:http-only true
+                         :path      "/"}
+                        (when (https-request? request)
+                          {:secure true}))]
+    (-> response
+        (resp/set-cookie metabase-embedded-session-cookie (str session-uuid) cookie-options)
+        (assoc-in [:headers anti-csrf-token-header] anti-csrf-token))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                                wrap-session-id                                                 |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(def ^:private ^String metabase-session-header "x-metabase-session")
+
+(defmulti ^:private wrap-session-id-with-strategy
+  "Attempt to add `:metabase-session-id` to `request` based on a specific strategy. Return modified request if
+  successful or `nil` if we should try another strategy."
+  {:arglists '([strategy request])}
+  (fn [strategy _]
+    strategy))
+
+(defmethod wrap-session-id-with-strategy :embedded-cookie
+  [_ {:keys [cookies headers], :as request}]
+  (when-let [session (get-in cookies [metabase-embedded-session-cookie :value])]
+    (when-let [anti-csrf-token (get headers anti-csrf-token-header)]
+      (assoc request :metabase-session-id session, :anti-csrf-token anti-csrf-token))))
+
+(defmethod wrap-session-id-with-strategy :normal-cookie
+  [_ {:keys [cookies], :as request}]
+  (when-let [session (get-in cookies [metabase-session-cookie :value])]
+    (when (seq session)
+      (assoc request :metabase-session-id session))))
+
+(defmethod wrap-session-id-with-strategy :header
+  [_ {:keys [headers], :as request}]
+  (when-let [session (get headers metabase-session-header)]
+    (when (seq session)
+      (assoc request :metabase-session-id session))))
+
+(defmethod wrap-session-id-with-strategy :best
+  [_ request]
+  (some
+   (fn [strategy]
+     (wrap-session-id-with-strategy strategy request))
+   [:embedded-cookie :normal-cookie :header]))
 
 (defn wrap-session-id
   "Middleware that sets the `:metabase-session-id` keyword on the request if a session id can be found.
-
-   We first check the request :cookies for `metabase.SESSION_ID`, then if no cookie is found we look in the
-   http headers for `X-METABASE-SESSION`.  If neither is found then then no keyword is bound to the request."
+   We first check the request :cookies for `metabase.SESSION`, then if no cookie is found we look in the http headers
+  for `X-METABASE-SESSION`. If neither is found then then no keyword is bound to the request."
   [handler]
   (fn [request respond raise]
-    (handler (wrap-session-id* request) respond raise)))
+    (let [request (or (wrap-session-id-with-strategy :best request)
+                      request)]
+      (handler request respond raise))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                             wrap-current-user-info                                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 ;; Because this query runs on every single API request it's worth it to optimize it a bit and only compile it to SQL
 ;; once rather than every time
-(def ^:private ^{:arglists '([db-type max-age-minutes])} session-with-id-query
+(def ^:private ^{:arglists '([db-type max-age-minutes session-type])} session-with-id-query
   (memoize
-   (fn [db-type max-age-minutes]
-     (vec
+   (fn [db-type max-age-minutes session-type]
+     (first
       (db/honeysql->sql
        {:select    [[:session.user_id :metabase-user-id]
                     [:user.is_superuser :is-superuser?]
@@ -142,22 +204,29 @@
                     [:= :user.is_active true]
                     [:= :session.id (hsql/raw "?")]
                     (let [oldest-allowed (sql.qp/add-interval-honeysql-form db-type :%now (- max-age-minutes) :minute)]
-                      [:> :session.created_at oldest-allowed])]
+                      [:> :session.created_at oldest-allowed])
+                    [:= :session.anti_csrf_token (case session-type
+                                                   :normal         nil
+                                                   :full-app-embed "?")]]
         :limit     1})))))
 
 (defn- current-user-info-for-session
   "Return User ID and superuser status for Session with `session-id` if it is valid and not expired."
-  [session-id]
+  [session-id anti-csrf-token]
   (when (and session-id (init-status/complete?))
-    (first
-     (jdbc/query (db/connection) (conj (session-with-id-query (mdb/db-type) (config/config-int :max-session-age))
-                                       session-id)))))
+    (let [sql    (session-with-id-query (mdb/db-type)
+                                        (config/config-int :max-session-age)
+                                        (if (seq anti-csrf-token) :full-app-embed :normal))
+          params (concat [session-id]
+                         (when (seq anti-csrf-token)
+                           [anti-csrf-token]))]
+      (first (jdbc/query (db/connection) (cons sql params))))))
 
-;; if someone passes in an `X-Metabase-Locale` header, use that for `user-locale` (overriding any value in the DB)
-(defn- merge-current-user-info [{:keys [metabase-session-id], {:strs [x-metabase-locale]} :headers, :as request}]
+(defn- merge-current-user-info
+  [{:keys [metabase-session-id anti-csrf-token], {:strs [x-metabase-locale]} :headers, :as request}]9
   (merge
    request
-   (current-user-info-for-session metabase-session-id)
+   (current-user-info-for-session metabase-session-id anti-csrf-token)
    (when x-metabase-locale
      (log/tracef "Found X-Metabase-Locale header: using %s as user locale" (pr-str x-metabase-locale))
      {:user-locale (i18n/normalized-locale-string x-metabase-locale)})))
@@ -168,6 +237,11 @@
   (fn [request respond raise]
     (handler (merge-current-user-info request) respond raise)))
 
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                               bind-current-user                                                |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
 (def ^:private current-user-fields
   (into [User] user/admin-or-self-visible-columns))
 
diff --git a/src/metabase/middleware/util.clj b/src/metabase/middleware/util.clj
index a1bbe9271cfb07dbdd9e7b5d2342948e785733a6..4fa6ef1189a367faa985433387b112ae8499fef7 100644
--- a/src/metabase/middleware/util.clj
+++ b/src/metabase/middleware/util.clj
@@ -30,3 +30,42 @@
              (re-matches #"^/app/dist/.*\.(js|css)$" uri))
         ;; GeoJSON proxy requests should also be cached
         (re-matches #"^/api/geojson/.*" uri))))
+
+(defn https-request?
+  "True if the original request made by the frontend client (i.e., browser) was made over HTTPS.
+
+  In many production instances, a reverse proxy such as an ELB or nginx will handle SSL termination, and the actual
+  request handled by Jetty will be over HTTP."
+  [{{:strs [x-forwarded-proto x-forwarded-protocol x-url-scheme x-forwarded-ssl front-end-https origin]} :headers
+    :keys                                                                                                [scheme]}]
+  (cond
+    ;; If `X-Forwarded-Proto` is present use that. There are several alternate headers that mean the same thing. See
+    ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
+    (or x-forwarded-proto x-forwarded-protocol x-url-scheme)
+    (= "https" (str/lower-case (or x-forwarded-proto x-forwarded-protocol x-url-scheme)))
+
+    ;; If none of those headers are present, look for presence of `X-Forwarded-Ssl` or `Frontend-End-Https`, which
+    ;; will be set to `on` if the original request was over HTTPS.
+    (or x-forwarded-ssl front-end-https)
+    (= "on" (str/lower-case (or x-forwarded-ssl front-end-https)))
+
+    ;; If none of the above are present, we are most not likely being accessed over a reverse proxy. Still, there's a
+    ;; good chance `Origin` will be present because it should be sent with `POST` requests, and most auth requests are
+    ;; `POST`. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
+    origin
+    (str/starts-with? (str/lower-case origin) "https")
+
+    ;; Last but not least, if none of the above are set (meaning there are no proxy servers such as ELBs or nginx in
+    ;; front of us), we can look directly at the scheme of the request sent to Jetty.
+    scheme
+    (= scheme :https)))
+
+(defn request-protocol
+  "Protocol of this request, either `:http` or `:https`."
+  [request]
+  (if (https-request? request) :https :http))
+
+(defn embedded?
+  "Whether this frontend client that made this request is embedded inside an `<iframe>`."
+  [request]
+  (some-> request (get-in [:headers "x-metabase-embedded"]) Boolean/parseBoolean))
diff --git a/src/metabase/models/collection.clj b/src/metabase/models/collection.clj
index d6c9de11a53158523b50aeaeb58606d92a0b128a..b5f75e600fb66cf87339a8ed51d31a312b098b07 100644
--- a/src/metabase/models/collection.clj
+++ b/src/metabase/models/collection.clj
@@ -12,6 +12,7 @@
              [interface :as i]
              [permissions :as perms :refer [Permissions]]]
             [metabase.models.collection.root :as collection.root]
+            [metabase.public-settings.metastore :as settings.metastore]
             [metabase.util :as u]
             [metabase.util
              [i18n :as ui18n :refer [trs tru]]
@@ -816,8 +817,8 @@
                      (db/select-one [Collection :id :namespace] :id (collection-or-id))
                      collection-or-id)]
     ;; HACK Collections in the "snippets" namespace have no-op permissions unless EE enhancements are enabled
-    ;; This code differs slightly in EE. We need to reconcile this when we do repo unification.
-    (if (= (u/qualified-name (:namespace collection)) "snippets")
+    (if (and (= (u/qualified-name (:namespace collection)) "snippets")
+             (not (settings.metastore/enable-enhancements?)))
       #{}
       ;; This is not entirely accurate as you need to be a superuser to modifiy a collection itself (e.g., changing its
       ;; name) but if you have write perms you can add/remove cards
diff --git a/src/metabase/models/collection/graph.clj b/src/metabase/models/collection/graph.clj
index f98d0b97a4d78b580100f75c98df6713f9d4bd17..338ce8a5f54bd8f728ce80555e188dffe4594cc4 100644
--- a/src/metabase/models/collection/graph.clj
+++ b/src/metabase/models/collection/graph.clj
@@ -34,7 +34,7 @@
 ;;; -------------------------------------------------- Fetch Graph ---------------------------------------------------
 
 (defn- group-id->permissions-set []
-  (into {} (for [[group-id perms] (group-by :group_id (db/select 'Permissions))]
+  (into {} (for [[group-id perms] (group-by :group_id (db/select Permissions))]
              {group-id (set (map :object perms))})))
 
 (s/defn ^:private perms-type-for-collection :- CollectionPermissions
@@ -46,9 +46,9 @@
 
 (s/defn ^:private group-permissions-graph :- GroupPermissionsGraph
   "Return the permissions graph for a single group having `permissions-set`."
-  [permissions-set collection-ids]
+  [collection-namespace permissions-set collection-ids]
   (into
-   {:root (perms-type-for-collection permissions-set collection/root-collection)}
+   {:root (perms-type-for-collection permissions-set (assoc collection/root-collection :namespace collection-namespace))}
    (for [collection-id collection-ids]
      {collection-id (perms-type-for-collection permissions-set collection-id)})))
 
@@ -59,9 +59,7 @@
   (let [personal-collection-ids (db/select-ids Collection :personal_owner_id [:not= nil])
         honeysql-form           (cond-> {:select [[:id :id]]
                                          :from   [Collection]
-                                         :where  (if (= collection-namespace ::all)
-                                                   [:= 1 1]
-                                                   [:= :namespace (u/qualified-name collection-namespace)])}
+                                         :where  [:= :namespace (u/qualified-name collection-namespace)]}
                                   (seq personal-collection-ids)
                                   (h/merge-where [:not-in :id (set personal-collection-ids)]))
         honeysql-form           (reduce
@@ -77,8 +75,7 @@
   function.
 
   The graph is restricted to a given namespace by the optional `collection-namespace` param; by default, `nil`, which
-  restricts it to the 'default' namespace containing normal Card/Dashboard/Pulse Collections. Pass `::all` to get a
-  combined graph of all namespaces, for the purposes of recording graph revisions.
+  restricts it to the 'default' namespace containing normal Card/Dashboard/Pulse Collections.
 
   Note: All Collections are returned at the same level of the 'graph', regardless of how the Collection hierarchy is
   structured. Collections do not inherit permissions from ancestor Collections in the same way data permissions are
@@ -93,17 +90,18 @@
          collection-ids  (non-personal-collection-ids collection-namespace)]
      {:revision (collection-revision/latest-id)
       :groups   (into {} (for [group-id (db/select-ids PermissionsGroup)]
-                           {group-id (group-permissions-graph (group-id->perms group-id) collection-ids)}))})))
+                           {group-id (group-permissions-graph collection-namespace (group-id->perms group-id) collection-ids)}))})))
 
 
 ;;; -------------------------------------------------- Update Graph --------------------------------------------------
 
 (s/defn ^:private update-collection-permissions!
-  [group-id             :- su/IntGreaterThanZero
+  [collection-namespace :- (s/maybe su/KeywordOrString)
+   group-id             :- su/IntGreaterThanZero
    collection-id        :- (s/cond-pre (s/eq :root) su/IntGreaterThanZero)
    new-collection-perms :- CollectionPermissions]
   (let [collection-id (if (= collection-id :root)
-                        collection/root-collection
+                        (assoc collection/root-collection :namespace collection-namespace)
                         collection-id)]
     ;; remove whatever entry is already there (if any) and add a new entry if applicable
     (perms/revoke-collection-permissions! group-id collection-id)
@@ -113,9 +111,11 @@
       :none  nil)))
 
 (s/defn ^:private update-group-permissions!
-  [group-id :- su/IntGreaterThanZero, new-group-perms :- GroupPermissionsGraph]
+  [collection-namespace :- (s/maybe su/KeywordOrString)
+   group-id             :- su/IntGreaterThanZero
+   new-group-perms      :- GroupPermissionsGraph]
   (doseq [[collection-id new-perms] new-group-perms]
-    (update-collection-permissions! group-id collection-id new-perms)))
+    (update-collection-permissions! collection-namespace group-id collection-id new-perms)))
 
 (defn- save-perms-revision!
   "Save changes made to the collection permissions graph for logging/auditing purposes. This doesn't do anything if
@@ -123,13 +123,13 @@
 
   *  `before-graph`-- the entire graph as it existed before the revision.
   *  `changes` -- set of changes applied in this revision."
-  [current-revision before-graph changes]
+  [collection-namespace current-revision before-graph changes]
   (when *current-user-id*
     ;; manually specify ID here so if one was somehow inserted in the meantime in the fraction of a second since we
     ;; called `check-revision-numbers` the PK constraint will fail and the transaction will abort
     (db/insert! CollectionRevision
       :id     (inc current-revision)
-      :before  before-graph
+      :before  (assoc before-graph :namespace collection-namespace)
       :after   changes
       :user_id *current-user-id*)))
 
@@ -142,20 +142,18 @@
 
   ([collection-namespace :- (s/maybe su/KeywordOrString), new-graph :- PermissionsGraph]
    (let [old-graph          (graph collection-namespace)
-         ;; fetch the *entire* graph before making any updates. We'll record this in the perms revision.
-         entire-graph       (graph ::all)
          old-perms          (:groups old-graph)
          new-perms          (:groups new-graph)
          ;; filter out any groups not in the old graph
          new-perms          (select-keys new-perms (keys old-perms))
          ;; filter out any collections not in the old graph
          new-perms          (into {} (for [[group-id collection-id->perms] new-perms]
-                                       [group-id (select-keys collection-id->perms (keys (get old-perms group-id)))]))
+                              [group-id (select-keys collection-id->perms (keys (get old-perms group-id)))]))
          [diff-old changes] (data/diff old-perms new-perms)]
      (perms/log-permissions-changes diff-old changes)
      (perms/check-revision-numbers old-graph new-graph)
      (when (seq changes)
        (db/transaction
          (doseq [[group-id changes] changes]
-           (update-group-permissions! group-id changes))
-         (save-perms-revision! (:revision old-graph) entire-graph changes))))))
+           (update-group-permissions! collection-namespace group-id changes))
+         (save-perms-revision! collection-namespace (:revision old-graph) old-graph changes))))))
diff --git a/src/metabase/models/collection/root.clj b/src/metabase/models/collection/root.clj
index 525e7e33486a033d4a1094fd6201c1a14618a4d2..0e4c1be9968e054774229bee3b1847d520b320d5 100644
--- a/src/metabase/models/collection/root.clj
+++ b/src/metabase/models/collection/root.clj
@@ -2,6 +2,7 @@
   (:require [metabase.models
              [interface :as i]
              [permissions :as perms]]
+            [metabase.public-settings.metastore :as settings.metastore]
             [metabase.util :as u]
             [potemkin.types :as p.types]
             [toucan.models :as models]))
@@ -19,8 +20,8 @@
 (defn- has-perms? [collection read-or-write]
   {:pre [(map? collection)]}
   ;; HACK Collections in the "snippets" namespace have no-op permissions unless EE enhancements are enabled
-  ;; This code differs slightly in EE. We need to reconcile this when we do repo unification.
-  (if (= (u/qualified-name (:namespace collection)) "snippets")
+  (if (and (= (u/qualified-name (:namespace collection)) "snippets")
+           (not (settings.metastore/enable-enhancements?)))
     #{}
     #{((case read-or-write
          :read  perms/collection-read-path
diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj
index 4f14f1482dbc9eb09762cd9c285c06b46d1ac56b..f2961b1f335cefb297f07dcf89bb8fcae17e38e4 100644
--- a/src/metabase/models/dashboard.clj
+++ b/src/metabase/models/dashboard.clj
@@ -21,7 +21,10 @@
              [revision :as revision]]
             [metabase.models.revision.diff :refer [build-sentence]]
             [metabase.query-processor.async :as qp.async]
-            [metabase.util.i18n :as ui18n]
+            [metabase.util
+             [i18n :as ui18n :refer [tru]]
+             [schema :as su]]
+            [schema.core :as s]
             [toucan
              [db :as db]
              [hydrate :refer [hydrate]]
@@ -49,6 +52,10 @@
 
 (models/defmodel Dashboard :report_dashboard)
 
+(defn- assert-valid-parameters [{:keys [parameters]}]
+  (when (s/check (s/maybe [{:id su/NonBlankString, s/Keyword s/Any}]) parameters)
+    (throw (ex-info (tru ":parameters must be a sequence of maps with String :id keys")
+                    {:parameters parameters}))))
 
 (defn- pre-delete [dashboard]
   (db/delete! 'Revision :model "Dashboard" :model_id (u/get-id dashboard)))
@@ -57,10 +64,12 @@
   (let [defaults  {:parameters []}
         dashboard (merge defaults dashboard)]
     (u/prog1 dashboard
+      (assert-valid-parameters dashboard)
       (collection/check-collection-namespace Dashboard (:collection_id dashboard)))))
 
 (defn- pre-update [dashboard]
   (u/prog1 dashboard
+    (assert-valid-parameters dashboard)
     (collection/check-collection-namespace Dashboard (:collection_id dashboard))))
 
 (u/strict-extend (class Dashboard)
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index 03f3bc29cc162612b128e5c6abdd7a1c17741cca..977fd70c63add2132b620472acc60715d6b02053 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -162,7 +162,7 @@
     (Field fk_target_field_id)))
 
 (defn values
-  "Return the `FieldValues` associated with this FIELD."
+  "Return the `FieldValues` associated with this `field`."
   [{:keys [id]}]
   (db/select [FieldValues :field_id :values], :field_id id))
 
@@ -178,7 +178,7 @@
                           (db/select model :field_id [:in field-ids])))))
 
 (defn with-values
-  "Efficiently hydrate the `FieldValues` for a collection of FIELDS."
+  "Efficiently hydrate the `FieldValues` for a collection of `fields`."
   {:batched-hydrate :values}
   [fields]
   (let [id->field-values (select-field-id->instance fields FieldValues)]
@@ -186,7 +186,7 @@
       (assoc field :values (get id->field-values (:id field) [])))))
 
 (defn with-normal-values
-  "Efficiently hydrate the `FieldValues` for visibility_type normal FIELDS."
+  "Efficiently hydrate the `FieldValues` for visibility_type normal `fields`."
   {:batched-hydrate :normal_values}
   [fields]
   (let [id->field-values (select-field-id->instance (filter fv/field-should-have-field-values? fields)
@@ -195,7 +195,7 @@
       (assoc field :values (get id->field-values (:id field) [])))))
 
 (defn with-dimensions
-  "Efficiently hydrate the `Dimension` for a collection of FIELDS."
+  "Efficiently hydrate the `Dimension` for a collection of `fields`."
   {:batched-hydrate :dimensions}
   [fields]
   ;; TODO - it looks like we obviously thought this code would return *all* of the Dimensions for a Field, not just
@@ -249,7 +249,7 @@
     (dissoc field :table)))
 
 (defn with-targets
-  "Efficiently hydrate the FK target fields for a collection of FIELDS."
+  "Efficiently hydrate the FK target fields for a collection of `fields`."
   {:batched-hydrate :target}
   [fields]
   (let [target-field-ids (set (for [field fields
@@ -264,7 +264,7 @@
 
 
 (defn qualified-name-components
-  "Return the pieces that represent a path to FIELD, of the form `[table-name parent-fields-name* field-name]`."
+  "Return the pieces that represent a path to `field`, of the form `[table-name parent-fields-name* field-name]`."
   [{field-name :name, table-id :table_id, parent-id :parent_id}]
   (conj (vec (if-let [parent (Field parent-id)]
                (qualified-name-components parent)
@@ -275,7 +275,7 @@
         field-name))
 
 (defn qualified-name
-  "Return a combined qualified name for FIELD, e.g. `table_name.parent_field_name.field_name`."
+  "Return a combined qualified name for `field`, e.g. `table_name.parent_field_name.field_name`."
   [field]
   (str/join \. (qualified-name-components field)))
 
diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj
index 6cccc1bd0f69574e7a6e8819cfc4bda1078fc971..60949d0b8112a82394fad0ea9cf69c32ebf520cd 100644
--- a/src/metabase/models/field_values.clj
+++ b/src/metabase/models/field_values.clj
@@ -3,7 +3,7 @@
             [metabase.plugins.classloader :as classloader]
             [metabase.util :as u]
             [metabase.util
-             [i18n :refer [trs]]
+             [i18n :refer [trs tru]]
              [schema :as su]]
             [schema.core :as s]
             [toucan
@@ -34,12 +34,50 @@
 
 (models/defmodel FieldValues :metabase_fieldvalues)
 
+(defn- assert-valid-human-readable-values [{human-readable-values :human_readable_values}]
+  (when (s/check (s/maybe [(s/maybe su/NonBlankString)]) human-readable-values)
+    (throw (ex-info (tru "Invalid human-readable-values: values must be a sequence; each item must be nil or a string")
+                    {:human-readable-values human-readable-values
+                     :status-code           400}))))
+
+(defn- pre-insert [field-values]
+  (u/prog1 field-values
+    (assert-valid-human-readable-values field-values)))
+
+(defn- pre-update [field-values]
+  (u/prog1 field-values
+    (assert-valid-human-readable-values field-values)))
+
+(defn- post-select [field-values]
+  (cond-> field-values
+    (contains? field-values :human_readable_values)
+    (update :human_readable_values (fn [human-readable-values]
+                                     (cond
+                                       (sequential? human-readable-values)
+                                       human-readable-values
+
+                                       ;; in some places human readable values were incorrectly saved as a map. If
+                                       ;; that's the case, convert them back to a sequence
+                                       (map? human-readable-values)
+                                       (do
+                                         (assert (:values field-values)
+                                                 (tru ":values must be present to fetch :human_readable_values"))
+                                         (mapv human-readable-values (:values field-values)))
+
+                                       ;; if the `:human_readable_values` key is present (i.e., if we are fetching the
+                                       ;; whole row), but `nil`, then replace the `nil` value with an empty vector. The
+                                       ;; client likes this better.
+                                       :else
+                                       [])))))
+
 (u/strict-extend (class FieldValues)
   models/IModel
   (merge models/IModelDefaults
          {:properties  (constantly {:timestamped? true})
-          :types       (constantly {:human_readable_values :json, :values :json})
-          :post-select (u/rpartial update :human_readable_values #(or % {}))}))
+          :types       (constantly {:human_readable_values :json-no-keywordization, :values :json})
+          :pre-insert  pre-insert
+          :pre-update  pre-update
+          :post-select post-select}))
 
 
 ;; ## FieldValues Helper Functions
@@ -61,18 +99,26 @@
   "`true` if the combined length of all the values in `distinct-values` is below the threshold for what we'll allow in a
   FieldValues entry. Does some logging as well."
   [distinct-values]
-  (let [total-length (reduce + (map (comp count str)
-                                    distinct-values))]
+  ;; only consume enough values to determine whether the total length is > `total-max-length` -- if it is, we can stop
+  (let [total-length (reduce
+                      (fn [total-length v]
+                        (let [new-total (+ total-length (count (str v)))]
+                          (if (>= new-total total-max-length)
+                            (reduced new-total)
+                            new-total)))
+                      0
+                      distinct-values)]
     (u/prog1 (<= total-length total-max-length)
-      (log/debug (trs "Field values total length is {0} (max {1})." total-length total-max-length)
+      (log/debug (trs "Field values total length is > {0}." total-max-length)
                  (if <>
                    (trs "FieldValues are allowed for this Field.")
                    (trs "FieldValues are NOT allowed for this Field."))))))
 
-
-(defn- distinct-values
+(defn distinct-values
   "Fetch a sequence of distinct values for `field` that are below the `total-max-length` threshold. If the values are
-  past the threshold, this returns `nil`."
+  past the threshold, this returns `nil`. (This function provides the values that normally get saved as a Field's
+  FieldValues. You most likely should not be using this directly in code outside of this namespace, unless it's for a
+  very specific reason, such as certain cases where we fetch ad-hoc FieldValues for GTAP-filtered Fields.)"
   [field]
   (classloader/require 'metabase.db.metadata-queries)
   (try
diff --git a/src/metabase/models/params.clj b/src/metabase/models/params.clj
index 13346bd117985ace70c3c310ef037d6027806254..9ae6f43dbe0ffc4c2be1c698a7447d8fc5073fc9 100644
--- a/src/metabase/models/params.clj
+++ b/src/metabase/models/params.clj
@@ -48,7 +48,7 @@
     :else
     (throw (IllegalArgumentException. (str (deferred-trs "Don't know how to wrap:") " " field-id-or-form)))))
 
-(s/defn ^:private field-ids->param-field-values
+(s/defn field-ids->param-field-values
   "Given a collection of `param-field-ids` return a map of FieldValues for the Fields they reference. This map is
   returned by various endpoints as `:param_values`."
   [param-field-ids :- (s/maybe #{su/IntGreaterThanZero})]
@@ -78,7 +78,6 @@
           (catch Throwable e
             (log/error e (tru "Could not find matching Field ID for target:") target)))))))
 
-
 (defn- pk-fields
   "Return the `fields` that are PK Fields."
   [fields]
diff --git a/src/metabase/models/permissions.clj b/src/metabase/models/permissions.clj
index 73f5acdfb419bc14b90b1d7e1c82134612c8473d..80026fdbd1eaec901b1d0f5226dd37bf09bed42d 100644
--- a/src/metabase/models/permissions.clj
+++ b/src/metabase/models/permissions.clj
@@ -286,21 +286,26 @@
   "Implementation of `IModel` `perms-objects-set` for models with a `collection_id`, such as Card, Dashboard, or Pulse.
   This simply returns the `perms-objects-set` of the parent Collection (based on `collection_id`), or for the Root
   Collection if `collection_id` is `nil`."
-  [this          :- {:collection_id (s/maybe su/IntGreaterThanZero), s/Keyword s/Any}
-   read-or-write :- (s/enum :read :write)]
-  ;; based on value of read-or-write determine the approprite function used to calculate the perms path
-  (let [path-fn (case read-or-write
-                  :read  collection-read-path
-                  :write collection-readwrite-path)]
-    ;; now pass that function our collection_id if we have one, or if not, pass it an object representing the Root
-    ;; Collection
-    #{(path-fn (or (:collection_id this)
-                   {:metabase.models.collection.root/is-root? true}))}))
+  ([this read-or-write]
+   (perms-objects-set-for-parent-collection nil this read-or-write))
+
+  ([collection-namespace :- (s/maybe su/KeywordOrString)
+    this                 :- {:collection_id (s/maybe su/IntGreaterThanZero), s/Keyword s/Any}
+    read-or-write        :- (s/enum :read :write)]
+   ;; based on value of read-or-write determine the approprite function used to calculate the perms path
+   (let [path-fn (case read-or-write
+                   :read  collection-read-path
+                   :write collection-readwrite-path)]
+     ;; now pass that function our collection_id if we have one, or if not, pass it an object representing the Root
+     ;; Collection
+     #{(path-fn (or (:collection_id this)
+                    {:metabase.models.collection.root/is-root? true
+                     :namespace                                collection-namespace}))})))
 
 (def IObjectPermissionsForParentCollection
-  "Implementation of `IObjectPermissions` for objects that have a `collection_id`, and thus, a parent Collection. Using
-  this will mean the current User is allowed to read or write these objects if they are allowed to read or write their
-  parent Collection."
+  "Implementation of `IObjectPermissions` for objects that have a `collection_id`, and thus, a parent Collection.
+   Using this will mean the current User is allowed to read or write these objects if they are allowed to read or
+  write their parent Collection."
   (merge i/IObjectPermissionsDefaults
          ;; TODO - we use these same partial implementations of `can-read?` and `can-write?` all over the place for
          ;; different models. Consider making them a mixin of some sort. (I was going to do this but I couldn't come
@@ -319,16 +324,14 @@
 (defn- pre-insert [permissions]
   (u/prog1 permissions
     (assert-valid permissions)
-    (log/debug (u/format-color 'green "Granting permissions for group %d: %s"
-                 (:group_id permissions) (:object permissions)))))
+    (log/debug (u/colorize 'green (trs "Granting permissions for group {0}: {1}" (:group_id permissions) (:object permissions))))))
 
 (defn- pre-update [_]
   (throw (Exception. (str (deferred-tru "You cannot update a permissions entry!")
                           (deferred-tru "Delete it and create a new one.")))))
 
 (defn- pre-delete [permissions]
-  (log/debug (u/format-color 'red "Revoking permissions for group %d: %s"
-               (:group_id permissions) (:object permissions)))
+  (log/debug (u/colorize 'red (trs "Revoking permissions for group {0}: {1}" (:group_id permissions) (:object permissions))))
   (assert-not-admin-group permissions))
 
 (u/strict-extend (class Permissions)
diff --git a/src/metabase/models/query.clj b/src/metabase/models/query.clj
index 97bde95ed36c6065d17eeff6c3a759624c44651b..5341ad931a84e9fbdeb19190ead89cb2af0f32b6 100644
--- a/src/metabase/models/query.clj
+++ b/src/metabase/models/query.clj
@@ -17,6 +17,7 @@
   (merge models/IModelDefaults
          {:types (constantly {:query :json})}))
 
+
 ;;; Helper Fns
 
 (defn average-execution-time-ms
diff --git a/src/metabase/models/query/permissions.clj b/src/metabase/models/query/permissions.clj
index 0dd7f95deb2acaebc976a47597d3857cfb7037cc..3b95a344ad267a6733a054765f3f885c73601d4d 100644
--- a/src/metabase/models/query/permissions.clj
+++ b/src/metabase/models/query/permissions.clj
@@ -22,20 +22,23 @@
 ;; Is calculating permissions for queries complicated? Some would say so. Refer to this handy flow chart to see how
 ;; things get calculated.
 ;;
-;;                   perms-set
-;;                        |
-;;                        |
-;;                        |
-;;   native query? <------+-----> mbql query?
-;;         ↓                           ↓
-;; adhoc-native-query-path     mbql-perms-path-set
-;;                                      |
-;;                no source card <------+----> has source card
-;;                        ↓                          ↓
-;;          tables->permissions-path-set   source-card-read-perms
-;;                        ↓
-;;                 table-query-path
+;;                      perms-set
+;;                           |
+;;                           |
+;;                           |
+;;      native query? <------+-----> mbql query?
+;;            ↓                           ↓
+;;    adhoc-native-query-path     mbql-perms-path-set
+;;                                         |
+;;                   no source card <------+----> has source card
+;;                           ↓                          ↓
+;;             tables->permissions-path-set   source-card-read-perms
+;;                           ↓
+;;                    table-query-path
 ;;
+;; `segmented-perms-set` follows the same graph as above, but instead of `table-query-path`, it returns
+;; `table-segmented-query-path`. `perms-set` will require full access to the tables, `segmented-perms-set` will only
+;; require segmented access
 
 (s/defn ^:private query->source-table-ids :- #{(s/cond-pre (s/eq ::native) su/IntGreaterThanZero)}
   "Return a sequence of all Table IDs referenced by `query`."
@@ -140,6 +143,12 @@
     (= (keyword query-type) :query)  (mbql-permissions-path-set query perms-opts)
     :else                            (throw (Exception. (tru "Invalid query type: {0}" query-type)))))
 
+(defn segmented-perms-set
+  "Calculate the set of permissions including segmented (not full) table permissions."
+  {:arglists '([query & {:keys [throw-exceptions? already-preprocessed?]}])}
+  [query & {:as perms-opts}]
+  (perms-set* query (assoc perms-opts :segmented-perms? true)))
+
 (defn perms-set
   "Calculate the set of permissions required to run an ad-hoc `query`. Returns permissions for full table access (not
   segmented)"
@@ -152,4 +161,5 @@
   permissions and segmented table permissions"
   [query]
   (let [user-perms @api/*current-user-permissions-set*]
-    (perms/set-has-full-permissions-for-set? user-perms (perms-set query))))
+    (or (perms/set-has-full-permissions-for-set? user-perms (perms-set query))
+        (perms/set-has-full-permissions-for-set? user-perms (segmented-perms-set query)))))
diff --git a/src/metabase/models/session.clj b/src/metabase/models/session.clj
index 99ab2611cb950d288d6b1d092b168792cc044f82..cc22958dda8eb04fd3c0a1dac4f8f0fe817a44e5 100644
--- a/src/metabase/models/session.clj
+++ b/src/metabase/models/session.clj
@@ -1,23 +1,35 @@
 (ns metabase.models.session
-  (:require [metabase.util :as u]
-            [toucan
-             [db :as db]
-             [models :as models]]))
+  (:require [buddy.core
+             [codecs :as codecs]
+             [nonce :as nonce]]
+            [metabase.middleware
+             [misc :as mw.misc]
+             [util :as mw.util]]
+            [metabase.util :as u]
+            [schema.core :as s]
+            [toucan.models :as models]))
+
+(s/defn ^:private random-anti-csrf-token :- #"^[0-9a-f]{32}$"
+  []
+  (codecs/bytes->hex (nonce/random-bytes 16)))
 
 (models/defmodel Session :core_session)
 
+(defn- pre-update [_]
+  (throw (RuntimeException. "You cannot update a Session.")))
+
 (defn- pre-insert [session]
-  (assoc session :created_at :%now))
+  (cond-> (assoc session :created_at :%now)
+    (some-> mw.misc/*request* mw.util/embedded?) (assoc :anti_csrf_token (random-anti-csrf-token))))
+
+(defn- post-insert [{anti-csrf-token :anti_csrf_token, :as session}]
+  (let [session-type (if anti-csrf-token :full-app-embed :normal)]
+    (assoc session :type session-type)))
 
 (u/strict-extend (class Session)
   models/IModel
-  (merge models/IModelDefaults
-         {:pre-insert pre-insert}))
-
-;; Persistence Functions
-
-(defn first-session-for-user
-  "Retrieves the first Session `:id` for a given user (if available), or nil otherwise."
-  ^String [user-id]
-  {:pre [(integer? user-id)]}
-  (db/select-one-id Session, :user_id user-id, {:order-by [[:created_at :asc]]}))
+  (merge
+   models/IModelDefaults
+   {:pre-insert  pre-insert
+    :post-insert post-insert
+    :pre-update  pre-update}))
diff --git a/src/metabase/plugins/classloader.clj b/src/metabase/plugins/classloader.clj
index 53f2836a35defd0659b4f8ed3e052ccf28269b97..a7f3c953a14906d180b1160477f186ac6dd21094 100644
--- a/src/metabase/plugins/classloader.clj
+++ b/src/metabase/plugins/classloader.clj
@@ -112,7 +112,9 @@
 (defn require
   "Just like vanilla `require`, but ensures we're using our shared classloader to do it. Always use this over vanilla
   `require` -- otherwise namespaces might get loaded by the wrong ClassLoader, resulting in weird, hard-to-debug
-  errors."
+  errors.
+
+  Added benefit -- this is also thread-safe, unlike vanilla require."
   [& args]
   ;; done for side-effects to ensure context classloader is the right one
   (the-classloader)
diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj
index 0815891a97287f76b115e3ecc14b10eee1ad80d0..7b7c1352da1cd656832c2f0e4e017fe9201f7705 100644
--- a/src/metabase/public_settings.clj
+++ b/src/metabase/public_settings.clj
@@ -10,7 +10,8 @@
             [metabase.models
              [common :as common]
              [setting :as setting :refer [defsetting]]]
-            metabase.public-settings.metastore
+            [metabase.plugins.classloader :as classloader]
+            [metabase.public-settings.metastore :as metastore]
             [metabase.util
              [i18n :as i18n :refer [available-locales-with-names deferred-tru trs tru]]
              [password :as password]]
@@ -20,6 +21,26 @@
 ;; These modules register settings but are otherwise unused. They still must be imported.
 (comment metabase.public-settings.metastore/keep-me)
 
+(defn- google-auth-configured? []
+  (boolean (setting/get :google-auth-client-id)))
+
+(defn- ldap-configured? []
+  (do (classloader/require 'metabase.integrations.ldap)
+      ((resolve 'metabase.integrations.ldap/ldap-configured?))))
+
+(defn- ee-sso-configured? []
+  (u/ignore-exceptions
+    (classloader/require 'metabase-enterprise.sso.integrations.sso-settings))
+  (when-let [varr (resolve 'metabase-enterprise.sso.integrations.sso-settings/other-sso-configured?)]
+    (varr)))
+
+(defn- sso-configured?
+  "Any SSO provider is configured"
+  []
+  (or (google-auth-configured?)
+      (ldap-configured?)
+      (ee-sso-configured?)))
+
 (defsetting check-for-updates
   (deferred-tru "Identify when new versions of Metabase are available.")
   :type    :boolean
@@ -111,6 +132,12 @@
   :default    "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
   :visibility :public)
 
+(defsetting landing-page
+  (deferred-tru "Default page to show the user")
+  :visibility :public
+  :type       :string
+  :default    "")
+
 (defsetting enable-public-sharing
   (deferred-tru "Enable admins to create publicly viewable links (and embeddable iframes) for Questions and Dashboards?")
   :type       :boolean
@@ -123,6 +150,10 @@
   :default    false
   :visibility :authenticated)
 
+(defsetting embedding-app-origin
+  (deferred-tru "Allow this origin to embed the full Metabase application")
+  :visibility :public)
+
 (defsetting enable-nested-queries
   (deferred-tru "Allow using a saved question as the source for other queries?")
   :type    :boolean
@@ -178,6 +209,44 @@
   :type    :integer
   :default 10)
 
+(defsetting application-name
+  (deferred-tru "This will replace the word \"Metabase\" wherever it appears.")
+  :visibility :public
+  :type       :string
+  :default    "Metabase")
+
+(defsetting application-colors
+  (deferred-tru "These are the primary colors used in charts and throughout Metabase. You might need to refresh your browser to see your changes take effect.")
+  :visibility :public
+  :type       :json
+  :default    {})
+
+(defn application-color
+  "The primary color, a.k.a. brand color"
+  []
+  (or (:brand (setting/get-json :application-colors)) "#509EE3"))
+
+(defsetting application-logo-url
+  (deferred-tru "For best results, use an SVG file with a transparent background.")
+  :visibility :public
+  :type       :string
+  :default    "app/assets/img/logo.svg")
+
+(defsetting application-favicon-url
+  (deferred-tru "The url or image that you want to use as the favicon.")
+  :visibility :public
+  :type       :string
+  :default    "frontend_client/favicon.ico")
+
+(defsetting enable-password-login
+  (deferred-tru "Allow logging in by email and password.")
+  :visibility :public
+  :type       :boolean
+  :default    true
+  :getter     (fn []
+                (or (setting/get-boolean :enable-password-login)
+                    (not (sso-configured?)))))
+
 (defsetting breakout-bins-num
   (deferred-tru "When using the default binning strategy and a number of bins is not provided, this number will be used as the default.")
   :type :integer
@@ -289,6 +358,16 @@
   :setter     :none
   :getter     (constantly config/mb-version-info))
 
+(defsetting premium-features
+  "Premium EE features enabled for this instance."
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] {:embedding  (metastore/hide-embed-branding?)
+                      :whitelabel (metastore/enable-whitelabeling?)
+                      :audit_app  (metastore/enable-audit-app?)
+                      :sandboxes  (metastore/enable-sandboxes?)
+                      :sso        (metastore/enable-sso?)}))
+
 (defsetting redirect-all-requests-to-https
   (deferred-tru "Force all traffic to use HTTPS via a redirect, if the site URL is HTTPS")
   :visibility :public
diff --git a/src/metabase/public_settings/metastore.clj b/src/metabase/public_settings/metastore.clj
index 3033d9543965b5ed8b89d3c03b60a4b4716871ec..5d9012ad883c75bbdc49b2b7cf6dcb59dc5ccf9f 100644
--- a/src/metabase/public_settings/metastore.clj
+++ b/src/metabase/public_settings/metastore.clj
@@ -1,6 +1,7 @@
 (ns metabase.public-settings.metastore
   "Settings related to checking token validity and accessing the MetaStore."
   (:require [cheshire.core :as json]
+            [clj-http.client :as http]
             [clojure.core.memoize :as memoize]
             [clojure.string :as str]
             [clojure.tools.logging :as log]
@@ -12,7 +13,8 @@
             [metabase.util
              [i18n :refer [deferred-tru trs tru]]
              [schema :as su]]
-            [schema.core :as s]))
+            [schema.core :as s]
+            [toucan.db :as db]))
 
 (def ^:private ValidToken
   "Schema for a valid metastore token. Must be 64 lower-case hex characters."
@@ -34,6 +36,11 @@
 ;;; |                                                TOKEN VALIDATION                                                |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
+(defn- active-user-count []
+  ;; NOTE: models.user imports public settings, which imports this namespace,
+  ;; so we can't import the User model here.
+  (db/count 'User :is_active true))
+
 (defn- token-status-url [token]
   (when (seq token)
     (format "%s/api/%s/v2/status" store-url token)))
@@ -58,26 +65,28 @@
   (log/info (trs "Checking with the MetaStore to see whether {0} is valid..." token))
   (deref
    (future
-     (log/debug (u/format-color 'green (trs "Using this URL to check token: {0}" (token-status-url token))))
+     (log/info (u/format-color 'green (trs "Using this URL to check token: {0}" (token-status-url token))))
      (try (some-> (token-status-url token)
-                  slurp
+                  (http/get {:query-params {:users (active-user-count)}})
+                  :body
                   (json/parse-string keyword))
-          ;; slurp will throw a FileNotFoundException for 404s, so in that case just return an appropriate
-          ;; 'Not Found' message
-          (catch java.io.FileNotFoundException e
-            {:valid false, :status (tru "Unable to validate token: 404 not found.")})
-          ;; if there was any other error fetching the token, log it and return a generic message about the
+          ;; if there was an error fetching the token, log it and return a generic message about the
           ;; token being invalid. This message will get displayed in the Settings page in the admin panel so
           ;; we do not want something complicated
-          (catch Throwable e
+          (catch clojure.lang.ExceptionInfo e
             (log/error e (trs "Error fetching token status:"))
-            {:valid false, :status (str (deferred-tru "There was an error checking whether this token was valid:")
-                                        " "
-                                        (.getMessage e))})))
+            (let [body (u/ignore-exceptions (some-> (ex-data e) :object :body (json/parse-string keyword)))]
+              (or
+               body
+               {:valid         false
+                :status        (tru "Unable to validate token")
+                :error-details (.getMessage e)})))))
    fetch-token-status-timeout-ms
-   {:valid false, :status (tru "Token validation timed out.")}))
+   {:valid         false
+    :status        (tru "Unable to validate token")
+    :error-details (tru "Token validation timed out.")}))
 
-(def ^:private ^{:arglists '([token])} fetch-token-status
+(def ^{:arglists '([token])} fetch-token-status
   "TTL-memoized version of `fetch-token-status*`. Caches API responses for 5 minutes. This is important to avoid making
   too many API calls to the Store, which will throttle us if we make too many requests; putting in a bad token could
   otherwise put us in a state where `valid-token->features*` made API calls over and over, never itself getting cached
@@ -88,10 +97,11 @@
 
 (s/defn ^:private valid-token->features* :- #{su/NonBlankString}
   [token :- ValidToken]
-  (let [{:keys [valid status features]} (fetch-token-status token)]
+  (let [{:keys [valid status features error-details]} (fetch-token-status token)]
     ;; if token isn't valid throw an Exception with the `:status` message
     (when-not valid
-      (throw (Exception. ^String status)))
+      (throw (ex-info status
+               {:status-code 400, :error-details error-details})))
     ;; otherwise return the features this token supports
     (set features)))
 
@@ -118,14 +128,16 @@
     (try
       (when (seq new-value)
         (when (s/check ValidToken new-value)
-          (throw (ex-info (tru "Token format is invalid. Token should be 64 hexadecimal characters.")
-                   {:status-code 400})))
+          (throw (ex-info (tru "Token format is invalid.")
+                   {:status-code 400, :error-details "Token should be 64 hexadecimal characters."})))
         (valid-token->features new-value)
         (log/info (trs "Token is valid.")))
       (setting/set-string! :premium-embedding-token new-value)
       (catch Throwable e
         (log/error e (trs "Error setting premium features token"))
-        (throw (ex-info (.getMessage e) {:status-code 400}))))))
+        (throw (ex-info (.getMessage e) (merge
+                                         {:message (.getMessage e), :status-code 400}
+                                         (ex-data e)))))))) ; merge in error-details if present
 
 (s/defn ^:private token-features :- #{su/NonBlankString}
   "Get the features associated with the system's premium features token."
@@ -134,7 +146,7 @@
     (or (some-> (premium-embedding-token) valid-token->features)
         #{})
     (catch Throwable e
-      (log/error (trs "Error validating token:") (.getMessage e))
+      (log/error e (trs "Error validating token"))
       #{})))
 
 (defsetting hide-embed-branding?
@@ -144,3 +156,44 @@
   :visibility :public
   :setter     :none
   :getter     (fn [] (boolean ((token-features) "embedding"))))
+
+(defsetting enable-whitelabeling?
+  "Should we allow full whitelabel embedding (reskinning the entire interface?)"
+  :type       :boolean
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (boolean ((token-features) "whitelabel"))))
+
+(defsetting enable-audit-app?
+  "Should we allow use of the audit app?"
+  :type       :boolean
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (boolean ((token-features) "audit-app"))))
+
+(defsetting enable-sandboxes?
+  "Should we enable data sandboxes (row and column-level permissions?"
+  :type       :boolean
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (boolean ((token-features) "sandboxes"))))
+
+(defsetting enable-sso?
+  "Should we enable SAML/JWT sign-in?"
+  :type       :boolean
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (boolean ((token-features) "sso"))))
+
+;; `enhancements` are not currently a specific "feature" that EE tokens can have or not have. Instead, it's a
+;; catch-all term for various bits of EE functionality that we assume all EE licenses include. (This may change in the
+;; future.)
+;;
+;; By checking whether `(token-features)` is non-empty we can see whether we have a valid EE token. If the token is
+;; valid, we can enable EE enhancements.
+(defsetting enable-enhancements?
+  "Should we various other enhancements, e.g. NativeQuerySnippet collection permissions?"
+  :type       :boolean
+  :visibility :public
+  :setter     :none
+  :getter     (fn [] (boolean (seq (token-features)))))
diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj
index 53edff865be5a981914c29968340a284136d6ec4..2a32fc4ede9d9ab56959bdf0f6b69a44cde0bbc9 100644
--- a/src/metabase/pulse.clj
+++ b/src/metabase/pulse.clj
@@ -30,18 +30,25 @@
 (defn execute-card
   "Execute the query for a single Card. `options` are passed along to the Query Processor."
   [{pulse-creator-id :creator_id} card-or-id & {:as options}]
+  ;; The Card must either be executed in the context of a User or by the MetaBot which itself is not a User
+  {:pre [(or (integer? pulse-creator-id)
+             (= (:context options) :metabot))]}
   (let [card-id (u/get-id card-or-id)]
     (try
       (when-let [{query :dataset_query, :as card} (Card :id card-id, :archived false)]
-        (let [query (assoc query :async? false)]
-          (session/with-current-user pulse-creator-id
-            {:card   card
-             :result (qp/process-query-and-save-with-max-results-constraints!
-                      query
-                      (merge {:executed-by pulse-creator-id
-                              :context     :pulse
-                              :card-id     card-id}
-                             options))})))
+        (let [query         (assoc query :async? false)
+              process-query (fn []
+                              (qp/process-query-and-save-with-max-results-constraints!
+                               query
+                               (merge {:executed-by pulse-creator-id
+                                       :context     :pulse
+                                       :card-id     card-id}
+                                      options)))]
+          {:card   card
+           :result (if pulse-creator-id
+                     (session/with-current-user pulse-creator-id
+                       (process-query))
+                     (process-query))}))
       (catch Throwable e
         (log/warn e (trs "Error running query for Card {0}" card-id))))))
 
@@ -266,7 +273,7 @@
        (send-pulse! pulse)                       Send to all Channels
        (send-pulse! pulse :channel-ids [312])    Send only to Channel with :id = 312"
   [{:keys [cards], :as pulse} & {:keys [channel-ids]}]
-  {:pre [(map? pulse)]}
+  {:pre [(map? pulse) (integer? (:creator_id pulse))]}
   (let [pulse (-> pulse
                   pulse/map->PulseInstance
                   ;; This is usually already done by this step, in the `send-pulses` task which uses `retrieve-pulse`
diff --git a/src/metabase/pulse/render/style.clj b/src/metabase/pulse/render/style.clj
index 58a51263c6198858795b73b6dd87b48c523a3c94..40b291f0914e95b976be0f77c3e53f1b4aced8d5 100644
--- a/src/metabase/pulse/render/style.clj
+++ b/src/metabase/pulse/render/style.clj
@@ -1,6 +1,7 @@
 (ns metabase.pulse.render.style
   "CSS styles and related helper code for Pulse rendering."
-  (:require [clojure.string :as str]))
+  (:require [clojure.string :as str]
+            [metabase.public-settings :as public-settings]))
 
 ;; TODO - we should move other CSS definitions from `metabase.pulse.render` namespaces into this one, so they're all
 ;; in one place.
@@ -66,9 +67,9 @@
 ;; don't try to improve the code and make this a plain variable, in EE it's customizable which is why it's a function.
 ;; Too much of a hassle to have it be a fn in one version of the code an a constant in another
 (defn primary-color
-  "Primary color to use in Pulses. For CE, this is always the classic Metabase blue."
+  "Primary color to use in Pulses; normally 'classic' MB blue, but customizable when whitelabeling is enabled."
   []
-  color-brand)
+  (public-settings/application-color))
 
 (defn font-style
   "Font family to use in rendered Pulses."
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index 4b1e2bd6de3e8782069e6cdceee988878462ded0..52105c675cf586fbdf5d5198f92684dcf2ef1fae 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -1,10 +1,17 @@
 (ns metabase.query-processor
-  "The main entrypoint to running queries."
+  "Primary entrypoints to running Metabase (MBQL) queries.
+
+    (metabase.query-processor/process-query {:type :query, :database 1, :query {:source-table 2}})
+
+  Various REST API endpoints, such as `POST /api/dataset`, return the results of queries; calling one variations of
+  `process-userland-query` (see documentation below)."
   (:require [clojure.tools.logging :as log]
             [metabase
              [config :as config]
-             [driver :as driver]]
+             [driver :as driver]
+             [util :as u]]
             [metabase.driver.util :as driver.u]
+            [metabase.plugins.classloader :as classloader]
             [metabase.query-processor
              [context :as context]
              [error-type :as error-type]
@@ -58,6 +65,12 @@
 ;;; |                                                QUERY PROCESSOR                                                 |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
+(u/ignore-exceptions
+  (classloader/require '[metabase-enterprise.audit.query-processor.middleware.handle-audit-queries :as ee.audit]
+                       '[metabase-enterprise.sandbox.query-processor.middleware
+                         [column-level-perms-check :as ee.sandbox.columns]
+                         [row-level-restrictions :as ee.sandbox.rows]]))
+
 ;; ▼▼▼ POST-PROCESSING ▼▼▼  happens from TOP-TO-BOTTOM, e.g. the results of `f` are (eventually) passed to `limit`
 (def default-middleware
   "The default set of middleware applied to queries ran via `process-query`."
@@ -70,6 +83,8 @@
    #'perms/check-query-permissions
    #'pre-alias-ags/pre-alias-aggregations
    #'cumulative-ags/handle-cumulative-aggregations
+   ;; yes, this is called a second time, because we need to handle any joins that got added
+   (resolve 'ee.sandbox.rows/apply-row-level-permissions)
    #'resolve-joined-fields/resolve-joined-fields
    #'resolve-joins/resolve-joins
    #'add-implicit-joins/add-implicit-joins
@@ -81,7 +96,9 @@
    #'resolve-fields/resolve-fields
    #'add-dim/add-remapping
    #'implicit-clauses/add-implicit-clauses
+   (resolve 'ee.sandbox.rows/apply-row-level-permissions)
    #'add-source-metadata/add-source-metadata-for-source-queries
+   (resolve 'ee.sandbox.columns/maybe-apply-column-level-perms-check)
    #'reconcile-bucketing/reconcile-breakout-and-order-by-bucketing
    #'bucket-datetime/auto-bucket-datetimes
    #'resolve-source-table/resolve-source-tables
@@ -97,6 +114,7 @@
    #'validate/validate-query
    #'normalize/normalize
    #'add-rows-truncated/add-rows-truncated
+   (resolve 'ee.audit/handle-internal-queries)
    #'results-metadata/record-and-return-metadata!])
 ;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP, e.g. the results of `expand-macros` are passed to
 ;; `substitute-parameters`
diff --git a/src/metabase/query_processor/middleware/add_source_metadata.clj b/src/metabase/query_processor/middleware/add_source_metadata.clj
index f0e9057cfc9f6e9c75cebaaaec522c9b76268738..d946a990b8d377f4701e212e8915b24b7cee83d1 100644
--- a/src/metabase/query_processor/middleware/add_source_metadata.clj
+++ b/src/metabase/query_processor/middleware/add_source_metadata.clj
@@ -44,9 +44,9 @@
          {:source-query source-query}))
       nil)))
 
-(s/defn ^:private mbql-source-query->metadata :- [mbql.s/SourceQueryMetadata]
+(s/defn mbql-source-query->metadata :- [mbql.s/SourceQueryMetadata]
   "Preprocess a `source-query` so we can determine the result columns."
-  [source-query]
+  [source-query :- mbql.s/MBQLQuery]
   (try
     (let [cols (binding [api/*current-user-id* nil]
                  ((resolve 'metabase.query-processor/query->expected-cols) {:database (:id (qp.store/database))
diff --git a/src/metabase/query_processor/middleware/annotate.clj b/src/metabase/query_processor/middleware/annotate.clj
index 5ad7c76870d709ab2dcda84dd7322e23275a3711..bd400b06f7520706d1d6d17d9612e74653ff41fb 100644
--- a/src/metabase/query_processor/middleware/annotate.clj
+++ b/src/metabase/query_processor/middleware/annotate.clj
@@ -161,7 +161,8 @@
     {:base_type    :type/Float
      :special_type :type/Number}))
 
-(s/defn ^:private col-info-for-field-clause :- {:field_ref mbql.s/Field, s/Keyword s/Any}
+(s/defn col-info-for-field-clause :- {:field_ref mbql.s/Field, s/Keyword s/Any}
+  "Return column metadata for a field clause such as `:field-id` or `:field-literal`."
   [{:keys [source-metadata expressions], :as inner-query} :- su/Map, clause :- mbql.s/Field]
   ;; for various things that can wrap Field clauses recurse on the wrapped Field but include a little bit of info
   ;; about the clause doing the wrapping
diff --git a/src/metabase/query_processor/middleware/permissions.clj b/src/metabase/query_processor/middleware/permissions.clj
index 72bdf3430c0625382bd456f8e09c121417ddaa16..3e59bf6a88388e722082589b7f782da59e1479ed 100644
--- a/src/metabase/query_processor/middleware/permissions.clj
+++ b/src/metabase/query_processor/middleware/permissions.clj
@@ -1,6 +1,7 @@
 (ns metabase.query-processor.middleware.permissions
   "Middleware for checking that the current user has permissions to run the current query."
-  (:require [clojure.tools.logging :as log]
+  (:require [clojure.set :as set]
+            [clojure.tools.logging :as log]
             [metabase.api.common :refer [*current-user-id* *current-user-permissions-set*]]
             [metabase.models
              [card :refer [Card]]
@@ -36,8 +37,13 @@
 (declare check-query-permissions*)
 
 (s/defn ^:private check-ad-hoc-query-perms
-  [outer-query]
-  (let [required-perms (query-perms/perms-set outer-query, :throw-exceptions? true, :already-preprocessed? true)]
+  [{:keys [gtap-perms], :as outer-query}]
+  ;; *If* we're using a GTAP, the User is obviously allowed to run its source query. So subtract the set of
+  ;; perms required to run the source query. (See further discussion in
+  ;; metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions)
+  (let [required-perms (set/difference
+                        (query-perms/perms-set outer-query, :throw-exceptions? true, :already-preprocessed? true)
+                        gtap-perms)]
     (log/tracef "Required ad-hoc perms: %s" (pr-str required-perms))
     (when-not (perms/set-has-full-permissions-for-set? @*current-user-permissions-set* required-perms)
       (throw (perms-exception required-perms)))
diff --git a/src/metabase/query_processor/reducible.clj b/src/metabase/query_processor/reducible.clj
index 20fc217177326efb0e65847e371dbb333c4b5da1..50376783faa5f9f2ed251b90e64bba510f39cf4e 100644
--- a/src/metabase/query_processor/reducible.clj
+++ b/src/metabase/query_processor/reducible.clj
@@ -41,7 +41,9 @@
   [middleware]
   (reduce
    (fn [qp middleware]
-     (middleware qp))
+     (if (some? middleware)
+       (middleware qp)
+       qp))
    pivot
    middleware))
 
@@ -135,6 +137,8 @@
 
 ;;; ------------------------------------------------- Other Util Fns -------------------------------------------------
 
+()
+
 (defn reducible-rows
   "Utility function for generating reducible rows when implementing `metabase.driver/execute-reducible-query`.
 
diff --git a/src/metabase/routes.clj b/src/metabase/routes.clj
index 8b61fa85cfcf0dc2502d1bebbe1597b7142dfa62..46201ff043852acb0a509fb250fbd7914b425615 100644
--- a/src/metabase/routes.clj
+++ b/src/metabase/routes.clj
@@ -11,9 +11,12 @@
              [dataset :as dataset-api]
              [routes :as api]]
             [metabase.core.initialization-status :as init-status]
+            [metabase.plugins.classloader :as classloader]
             [metabase.routes.index :as index]
             [ring.util.response :as resp]))
 
+(u/ignore-exceptions (classloader/require '[metabase-enterprise.sso.api.routes :as ee.sso.routes]))
+
 (defn- redirect-including-query-string
   "Like `resp/redirect`, but passes along query string URL params as well. This is important because the public and
    embedding routes below pass query params (such as template tags) as part of the URL."
@@ -21,7 +24,6 @@
   (fn [{:keys [query-string]} respond _]
     (respond (resp/redirect (str url "?" query-string)))))
 
-
 ;; /public routes. /public/question/:uuid.:export-format redirects to /api/public/card/:uuid/query/:export-format
 (defroutes ^:private public-routes
   (GET ["/question/:uuid.:export-format", :uuid u/uuid-regex, :export-format dataset-api/export-format-regex]
@@ -29,7 +31,6 @@
        (redirect-including-query-string (format "%s/api/public/card/%s/query/%s" (public-settings/site-url) uuid export-format)))
   (GET "*" [] index/public))
 
-
 ;; /embed routes. /embed/question/:token.:export-format redirects to /api/public/card/:token/query/:export-format
 (defroutes ^:private embed-routes
   (GET ["/question/:token.:export-format", :export-format dataset-api/export-format-regex]
@@ -37,18 +38,21 @@
        (redirect-including-query-string (format "%s/api/embed/card/%s/query/%s" (public-settings/site-url) token export-format)))
   (GET "*" [] index/embed))
 
-
-;; Redirect naughty users who try to visit a page other than setup if setup is not yet complete
 (defroutes ^{:doc "Top-level ring routes for Metabase."} routes
+  (or (some-> (resolve 'ee.sso.routes/routes) var-get)
+      (fn [_ respond _]
+        (respond nil)))
   ;; ^/$ -> index.html
   (GET "/" [] index/index)
-  (GET "/favicon.ico" [] (resp/resource-response "frontend_client/favicon.ico"))
+  (GET "/favicon.ico" [] (resp/resource-response (public-settings/application-favicon-url)))
   ;; ^/api/health -> Health Check Endpoint
   (GET "/api/health" [] (if (init-status/complete?)
                           {:status 200, :body {:status "ok"}}
                           {:status 503, :body {:status "initializing", :progress (init-status/progress)}}))
   ;; ^/api/ -> All other API routes
   (context "/api" [] (fn [& args]
+                       ;; Redirect naughty users who try to visit a page other than setup if setup is not yet complete
+                       ;;
                        ;; if Metabase is not finished initializing, return a generic error message rather than
                        ;; something potentially confusing like "DB is not set up"
                        (if-not (init-status/complete?)
diff --git a/src/metabase/routes/index.clj b/src/metabase/routes/index.clj
index a5bcb926f04b8301ed1fe549afc2d8a8c7b39b86..025e84e5914f9d9ba98391ae4b70b9745f0cc15a 100644
--- a/src/metabase/routes/index.clj
+++ b/src/metabase/routes/index.clj
@@ -78,6 +78,8 @@
       :googleAnalyticsJS  (load-inline-js "index_ganalytics")
       :bootstrapJSON      (escape-script (json/generate-string public-settings))
       :localizationJSON   (escape-script (load-localization))
+      :favicon            (h.util/escape-html (public-settings/application-favicon-url))
+      :applicationName    (h.util/escape-html (public-settings/application-name))
       :uri                (h.util/escape-html uri)
       :baseHref           (h.util/escape-html (base-href))
       :embedCode          (when embeddable? (embed/head uri))
diff --git a/src/metabase/util.clj b/src/metabase/util.clj
index 4b1348f653ba94c3d1d3a9c2527489dcfd90f4f6..e9519c8819e282481c2d699e008c7b505ee00320 100644
--- a/src/metabase/util.clj
+++ b/src/metabase/util.clj
@@ -24,14 +24,6 @@
            javax.xml.bind.DatatypeConverter
            [org.apache.commons.validator.routines RegexValidator UrlValidator]))
 
-;; This is the very first log message that will get printed.
-;;
-;; It's here because this is one of the very first namespaces that gets loaded, and the first that has access to the
-;; logger It shows up a solid 10-15 seconds before the "Starting Metabase in STANDALONE mode" message because so many
-;; other namespaces need to get loaded
-(when-not *compile-files*
-  (log/info (trs "Loading Metabase...")))
-
 (defn format-bytes
   "Nicely format `num-bytes` as kilobytes/megabytes/etc.
 
@@ -560,7 +552,7 @@
   "Is `s` a Base-64 encoded string?"
   ^Boolean [s]
   (boolean (when (string? s)
-             (re-find #"^[0-9A-Za-z/+]+=*$" s))))
+             (re-matches #"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" s))))
 
 (defn decode-base64
   "Decodes a Base64 string to a UTF-8 string"
diff --git a/src/metabase/util/cron.clj b/src/metabase/util/cron.clj
index b92cf51922d3cf175a24048c80a522d7a115c25d..85879d7aa61dc752751b39b3c65a6c423b5b0c82 100644
--- a/src/metabase/util/cron.clj
+++ b/src/metabase/util/cron.clj
@@ -3,9 +3,12 @@
    See http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html#format for details on cron
    format."
   (:require [clojure.string :as str]
-            [metabase.util.schema :as su]
+            [metabase.util
+             [i18n :as i18n]
+             [schema :as su]]
             [schema.core :as s])
-  (:import org.quartz.CronExpression))
+  (:import net.redhogs.cronparser.CronExpressionDescriptor
+           org.quartz.CronExpression))
 
 (def CronScheduleString
   "Schema for a valid cron schedule string."
@@ -139,3 +142,8 @@
      :schedule_frame (cron-day-of-week+day-of-month->frame day-of-week day-of-month)
      :schedule_hour  (cron->hour hours)
      :schedule_type  (cron->schedule-type hours day-of-month day-of-week)}))
+
+(s/defn describe-cron-string :- su/NonBlankString
+  "Return a human-readable description of a cron expression, localized for the current User."
+  [cron-string :- CronScheduleString]
+  (CronExpressionDescriptor/getDescription ^String cron-string, ^java.util.Locale (i18n/user-locale)))
diff --git a/src/metabase/util/files.clj b/src/metabase/util/files.clj
index ccdb6b06573f80dff6f9c743f8da5da3b474f2d7..9266a7dece33713dee8667e4c876ebde0ba33290 100644
--- a/src/metabase/util/files.clj
+++ b/src/metabase/util/files.clj
@@ -77,9 +77,9 @@
 (defn- copy-file! [^Path source, ^Path dest]
   (when (or (not (exists? dest))
             (not= (last-modified-timestamp source) (last-modified-timestamp dest)))
-    (u/profile (trs "Extract file {0} -> {1}" source dest)
-      (Files/copy source dest (u/varargs CopyOption [StandardCopyOption/REPLACE_EXISTING
-                                                     StandardCopyOption/COPY_ATTRIBUTES])))))
+    (log/info (trs "Extract file {0} -> {1}" source dest))
+    (Files/copy source dest (u/varargs CopyOption [StandardCopyOption/REPLACE_EXISTING
+                                                   StandardCopyOption/COPY_ATTRIBUTES]))))
 
 (defn copy-files!
   "Copy all files in `source-dir` to `dest-dir`. Overwrites existing files if last modified timestamp is not the same as
diff --git a/src/metabase/util/schema.clj b/src/metabase/util/schema.clj
index 45fa82d6439da65f6ea61d0ad77cb3eee1a16f5c..fe1d05203a252ce522e300be449a8f7d1bfdb085 100644
--- a/src/metabase/util/schema.clj
+++ b/src/metabase/util/schema.clj
@@ -6,7 +6,9 @@
              [string :as str]
              [walk :as walk]]
             [medley.core :as m]
-            [metabase.util :as u]
+            [metabase
+             [types :as types]
+             [util :as u]]
             [metabase.util
              [i18n :as i18n :refer [deferred-tru]]
              [password :as password]]
@@ -15,6 +17,9 @@
              [macros :as s.macros]
              [utils :as s.utils]]))
 
+;; So the `:type/` hierarchy is loaded.
+(comment types/keep-me)
+
 ;; always validate all schemas in s/defn function declarations. See
 ;; https://github.com/plumatic/schema#schemas-in-practice for details.
 (s/set-fn-validation! true)
diff --git a/test/expectations.clj b/test/expectations.clj
index 4b74869b63434c95690857639d40f1fb6aa3b7ac..4d1518722555c41efa55d232010b7385b212019c 100644
--- a/test/expectations.clj
+++ b/test/expectations.clj
@@ -137,8 +137,10 @@
     ;; expecting it.
     (when-not (env/env :drivers)
       (t/testing "Don't write any new tests using expect!"
-        (t/is (<= total-expect-forms 882))
-        (t/is (<= total-namespaces-using-expect 85))))))
+        (let [ee? (u/ignore-exceptions (require 'metabase-enterprise.core) true)]
+          ;; TODO - update the numbers for EE
+          (t/is (<= total-expect-forms (if ee? 1177 882)))
+          (t/is (<= total-namespaces-using-expect (if ee? 107 85))))))))
 
 (defmacro ^:deprecated expect
   "Simple macro that simulates converts an Expectations-style `expect` form into a `clojure.test` `deftest` form."
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 46f59f14b6b686cf9cc97957afe8ff7f36b870f2..dab25f0d413e4cc4ea00eae5a1e88b5b81f75c8f 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -9,7 +9,8 @@
             [medley.core :as m]
             [metabase
              [http-client :as http :refer :all]
-             [models :refer [Card CardFavorite Collection Dashboard Database Pulse PulseCard PulseChannel PulseChannelRecipient Table ViewLog]]
+             [models :refer [Card CardFavorite Collection Dashboard Database Pulse PulseCard PulseChannel
+                             PulseChannelRecipient Table ViewLog]]
              [test :as mt]
              [util :as u]]
             [metabase.api.card :as card-api]
@@ -51,7 +52,7 @@
    :cache_ttl           nil
    :result_metadata     nil})
 
-(defn- mbql-count-query
+(defn mbql-count-query
   ([]
    (mbql-count-query (mt/id) (mt/id :venues)))
 
@@ -60,12 +61,13 @@
     :type     :query
     :query    {:source-table (u/get-id table-or-id), :aggregation [[:count]]}}))
 
-(defn- card-with-name-and-query
+(defn card-with-name-and-query
   ([]
    (card-with-name-and-query (mt/random-name)))
 
   ([card-name]
    (card-with-name-and-query card-name (mbql-count-query)))
+
   ([card-name query]
    {:name                   card-name
     :display                "scalar"
@@ -479,7 +481,6 @@
             (is (nil? (some-> (db/select-one [Card :collection_id :collection_position] :name card-name)
                               (update :collection_id (partial = (u/get-id collection))))))))))))
 
-
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                            FETCHING A SPECIFIC CARD                                            |
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/test/metabase/api/common_test.clj b/test/metabase/api/common_test.clj
index bccd4913141361316e5deb3704143f2261504fd8..bf177bd37a96d4ba03ca6983d6669b0aa7a0313f 100644
--- a/test/metabase/api/common_test.clj
+++ b/test/metabase/api/common_test.clj
@@ -16,7 +16,8 @@
   {:status  404
    :body    "Not found."
    :headers {"Cache-Control"                     "max-age=0, no-cache, must-revalidate, proxy-revalidate"
-             "Content-Security-Policy"           (-> @#'mw.security/content-security-policy-header vals first)
+             "Content-Security-Policy"           (str (-> @#'mw.security/content-security-policy-header vals first)
+                                                      " frame-ancestors 'none';")
              "Content-Type"                      "text/plain"
              "Expires"                           "Tue, 03 Jul 2001 06:00:00 GMT"
              "Last-Modified"                     true ; this will be current date, so do update-in ... string?
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index 7b7bba3e7e4e0b10def4ad05151eb9e79250ac64..716946d9c946221bc14c6cfb8d71d5917bc0e26c 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -239,7 +239,7 @@
                            :creator_id    (mt/user->id :rasta)
                            :collection_id true
                            :can_write     false
-                           :param_values  {}
+                           :param_values  nil
                            :param_fields  {(keyword (str field-id)) {:id               field-id
                                                                      :table_id         table-id
                                                                      :display_name     display-name
diff --git a/test/metabase/api/embed_test.clj b/test/metabase/api/embed_test.clj
index aa2fe502a792f5a4816987aaf04f2d847a141c84..ae77c41071599e9b06828ca37a69fdaf84dbd176 100644
--- a/test/metabase/api/embed_test.clj
+++ b/test/metabase/api/embed_test.clj
@@ -364,19 +364,19 @@
         (is (= "Message seems corrupt or manipulated."
                (http/client :get 400 (with-new-secret-key (dashboard-url dash)))))))))
 
-
 (deftest only-enabled-params-that-are-not-present-in-the-jwt-come-back
   (testing "check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back"
     (with-embedding-enabled-and-new-secret-key
       (tt/with-temp Dashboard [dash {:enable_embedding true
                                      :embedding_params {:a "locked", :b "disabled", :c "enabled", :d "enabled"}
-                                     :parameters       [{:slug "a", :name "a", :type "date"}
-                                                        {:slug "b", :name "b", :type "date"}
-                                                        {:slug "c", :name "c", :type "date"}
-                                                        {:slug "d", :name "d", :type "date"}]}]
-        (is (= [{:slug "d", :name "d", :type "date"}]
+                                     :parameters       [{:id "_a", :slug "a", :name "a", :type "date"}
+                                                        {:id "_b", :slug "b", :name "b", :type "date"}
+                                                        {:id "_c", :slug "c", :name "c", :type "date"}
+                                                        {:id "_d", :slug "d", :name "d", :type "date"}]}]
+        (is (= [{:id "_d", :slug "d", :name "d", :type "date"}]
                (:parameters (http/client :get 200 (dashboard-url dash {:params {:c 100}})))))))))
 
+
 ;;; ---------------------- GET /api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id -----------------------
 
 (defn- dashcard-url [dashcard & [additional-token-params]]
diff --git a/test/metabase/api/preview_embed_test.clj b/test/metabase/api/preview_embed_test.clj
index 03b48179c79ed6bc404ae15251f3350cbb528e1a..21576edba2ac4abac37d4c39c8dc6cbafd88a43f 100644
--- a/test/metabase/api/preview_embed_test.clj
+++ b/test/metabase/api/preview_embed_test.clj
@@ -1,5 +1,6 @@
 (ns metabase.api.preview-embed-test
-  (:require [expectations :refer :all]
+  (:require [clojure.test :refer :all]
+            [expectations :refer :all]
             [metabase.api.embed-test :as embed-test]
             [metabase.models
              [card :refer [Card]]
@@ -212,20 +213,20 @@
     (tt/with-temp Dashboard [dash]
       ((test-users/user->client :crowberto) :get 400 (embed-test/with-new-secret-key (dashboard-url dash))))))
 
-;; Check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back
-(expect
-  [{:slug "d", :name "d", :type "date"}]
-  (embed-test/with-embedding-enabled-and-new-secret-key
-    (tt/with-temp Dashboard [dash {:parameters [{:slug "a", :name "a", :type "date"}
-                                                {:slug "b", :name "b", :type "date"}
-                                                {:slug "c", :name "c", :type "date"}
-                                                {:slug "d", :name "d", :type "date"}]}]
-      (:parameters ((test-users/user->client :crowberto) :get 200 (dashboard-url dash
-                                                                    {:params            {:c 100},
-                                                                     :_embedding_params {:a "locked"
-                                                                                         :b "disabled"
-                                                                                         :c "enabled"
-                                                                                         :d "enabled"}}))))))
+(deftest only-enabled-params-not-in-jwt-test
+  (testing "Check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back"
+    (embed-test/with-embedding-enabled-and-new-secret-key
+      (tt/with-temp Dashboard [dash {:parameters [{:id "_a", :slug "a", :name "a", :type "date"}
+                                                  {:id "_b", :slug "b", :name "b", :type "date"}
+                                                  {:id "_c", :slug "c", :name "c", :type "date"}
+                                                  {:id "_d", :slug "d", :name "d", :type "date"}]}]
+        (is (= [{:id "_d", :slug "d", :name "d", :type "date"}]
+               (:parameters ((test-users/user->client :crowberto) :get 200 (dashboard-url dash
+                                                                             {:params            {:c 100}
+                                                                              :_embedding_params {:a "locked"
+                                                                                                  :b "disabled"
+                                                                                                  :c "enabled"
+                                                                                                  :d "enabled"}})))))))))
 
 
 ;;; ------------------ GET /api/preview_embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id -------------------
diff --git a/test/metabase/api/public_test.clj b/test/metabase/api/public_test.clj
index 6cf73c945da9d199233c5f7e068e5a1447ab5857..15471ddfd16b10bd98a0bac9a9ff60dca662af89 100644
--- a/test/metabase/api/public_test.clj
+++ b/test/metabase/api/public_test.clj
@@ -40,7 +40,8 @@
 
 (defmacro ^:private with-temp-public-dashboard {:style/indent 1} [[binding & [dashboard]] & body]
   `(let [dashboard-settings# (merge
-                              {:parameters [{:name    "Venue ID"
+                              {:parameters [{:id      "_VENUE_ID_"
+                                             :name    "Venue ID"
                                              :slug    "venue_id"
                                              :type    "id"
                                              :target  [:dimension (mt/id :venues :id)]
@@ -109,8 +110,8 @@
                                                                    :widget-type  "category"
                                                                    :required     true}}}}}]
     (is (= {(mt/id :categories :name) {:values                75
-                                         :human_readable_values {}
-                                         :field_id              (mt/id :categories :name)}}
+                                       :human_readable_values []
+                                       :field_id              (mt/id :categories :name)}}
            (-> (:param_values (#'public-api/public-card :id (u/get-id card)))
                (update-in [(mt/id :categories :name) :values] count)
                (update (mt/id :categories :name) #(into {} %)))))))
@@ -179,9 +180,13 @@
 (deftest check-that-we-can-exec-a-publiccard-with---parameters-
   (mt/with-temporary-setting-values [enable-public-sharing true]
     (with-temp-public-card [{uuid :public_uuid}]
-      (is (= [{:name "Venue ID", :slug "venue_id", :type "id", :value 2}]
+      (is (= [{:id "_VENUE_ID_", :name "Venue ID", :slug "venue_id", :type "id", :value 2}]
              (get-in (http/client :get 202 (str "public/card/" uuid "/query")
-                                  :parameters (json/encode [{:name "Venue ID", :slug "venue_id", :type "id", :value 2}]))
+                                  :parameters (json/encode [{:id    "_VENUE_ID_"
+                                                             :name  "Venue ID"
+                                                             :slug  "venue_id"
+                                                             :type  "id"
+                                                             :value 2}]))
                      [:json_query :parameters]))))))
 
 ;; Cards with required params
@@ -237,7 +242,8 @@
     (mt/with-temp Card [{uuid :public_uuid} (card-with-date-field-filter)]
       (is (= "count\n107\n"
              (http/client :get 200 (str "public/card/" uuid "/query/csv")
-                          :parameters (json/encode [{:type   :date/quarter-year
+                          :parameters (json/encode [{:id     "_DATE_"
+                                                     :type   :date/quarter-year
                                                      :target [:dimension [:template-tag :date]]
                                                      :value  "Q1-2014"}])))))))
 
@@ -250,7 +256,8 @@
         (mt/with-temporary-setting-values [site-url http/*url-prefix*]
           (is (= "count\n107\n"
                  (http/client :get 200 (str "public/question/" uuid ".csv")
-                              :parameters (json/encode [{:type   :date/quarter-year
+                              :parameters (json/encode [{:id     "_DATE_"
+                                                         :type   :date/quarter-year
                                                          :target [:dimension [:template-tag :date]]
                                                          :value  "Q1-2014"}])))))))))
 
@@ -350,7 +357,8 @@
                (mt/rows (http/client :get 202 (dashcard-url dash card)))))
 
         (testing "with parameters"
-          (is (= [{:name    "Venue ID"
+          (is (= [{:id      "_VENUE_ID_"
+                   :name    "Venue ID"
                    :slug    "venue_id"
                    :target  ["dimension" (mt/id :venues :id)]
                    :value   [10]
@@ -540,11 +548,11 @@
 
 (defn- price-param-values []
   {(keyword (str (mt/id :venues :price))) {:values                [1 2 3 4]
-                                             :human_readable_values {}
-                                             :field_id              (mt/id :venues :price)}})
+                                           :human_readable_values []
+                                           :field_id              (mt/id :venues :price)}})
 
 (defn- add-price-param-to-dashboard! [dashboard]
-  (db/update! Dashboard (u/get-id dashboard) :parameters [{:name "Price", :type "category", :slug "price"}]))
+  (db/update! Dashboard (u/get-id dashboard) :parameters [{:name "Price", :type "category", :slug "price", :id "_PRICE_"}]))
 
 (defn- add-dimension-param-mapping-to-dashcard! [dashcard card dimension]
   (db/update! DashboardCard (u/get-id dashcard) :parameter_mappings [{:card_id (u/get-id card)
diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj
index e7dbe978176a42e54f10d1c93cebb2c81b4cbc7c..8d4958a7cad8f55ff9e995bdc0ee9edb9e80559b 100644
--- a/test/metabase/api/session_test.clj
+++ b/test/metabase/api/session_test.clj
@@ -444,9 +444,9 @@
     (mt/with-temp User [user {:email "cam@sf-toucannery.com"}]
       (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"]
         (testing "their account should return a Session"
-          (is (instance?
-               UUID
-               (#'session-api/google-auth-fetch-or-create-user! "Cam" "Saul" "cam@sf-toucannery.com")))))))
+          (is (schema= {:id       UUID
+                        s/Keyword s/Any}
+                       (#'session-api/google-auth-fetch-or-create-user! "Cam" "Saul" "cam@sf-toucannery.com")))))))
 
   (testing "test that a user that doesn't exist with a *different* domain than the auto-create accounts domain gets an exception"
     (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain nil
@@ -460,9 +460,9 @@
       (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com"
                                          admin-email                             "rasta@toucans.com"]
         (try
-          (is (instance?
-               UUID
-               (#'session-api/google-auth-fetch-or-create-user! "Rasta" "Toucan" "rasta@sf-toucannery.com")))
+          (is (schema= {:id       UUID
+                        s/Keyword s/Any}
+                       (#'session-api/google-auth-fetch-or-create-user! "Rasta" "Toucan" "rasta@sf-toucannery.com")))
           (finally
             (db/delete! User :email "rasta@sf-toucannery.com")))))))
 
@@ -472,8 +472,13 @@
 (deftest ldap-login-test
   (ldap.test/with-ldap-server
     (testing "Test that we can login with LDAP"
-      (is (schema= SessionResponse
-                   (mt/client :post 200 "session" (mt/user->credentials :rasta)))))
+      (let [user-id (test-users/user->id :rasta)]
+        (try
+          (db/simple-delete! Session :user_id user-id)
+          (is (schema= SessionResponse
+                       (mt/client :post 200 "session" (mt/user->credentials :rasta))))
+          (finally
+            (db/update! User user-id :login_attributes nil)))))
 
     (testing "Test that login will fallback to local for users not in LDAP"
       (is (schema= SessionResponse
@@ -486,9 +491,15 @@
 
     (testing "Test that login will fallback to local for broken LDAP settings"
       (mt/with-temporary-setting-values [ldap-user-base "cn=wrong,cn=com"]
-        (is (schema= SessionResponse
-                     (mt/suppress-output
-                       (mt/client :post 200 "session" (mt/user->credentials :rasta)))))))
+        ;; delete all other sessions for the bird first, otherwise test doesn't seem to work (TODO - why?)
+        (let [user-id (test-users/user->id :rasta)]
+          (try
+            (db/simple-delete! Session :user_id user-id)
+            (is (schema= SessionResponse
+                         (mt/suppress-output
+                          (mt/client :post 200 "session" (mt/user->credentials :rasta)))))
+            (finally
+              (db/update! User user-id :login_attributes nil))))))
 
     (testing "Test that we can login with LDAP with new user"
       (try
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 79a1d8b65e438a83b514ed28a418d3cc0b416af5..6924b6f8a54e1ac6bec624487ccec49152a3ebfc 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -519,8 +519,7 @@
 
 (deftest query-metadata-remappings-test
   (testing "GET /api/table/:id/query_metadata"
-    (mt/with-temp-objects
-      (data/create-venue-category-remapping! "Foo")
+    (data/with-venue-category-remapping "Foo"
       (testing "Ensure internal remapped dimensions and human_readable_values are returned"
         (is (= [{:table_id   (mt/id :venues)
                  :id         (mt/id :venues :category_id)
@@ -551,8 +550,7 @@
                   (narrow-fields ["PRICE" "CATEGORY_ID"]
                                  ((mt/user->client :rasta) :get 200 (format "table/%d/query_metadata" (mt/id :venues))))))))))
 
-    (mt/with-temp-objects
-      (data/create-venue-category-fk-remapping! "Foo")
+    (data/with-venue-category-fk-remapping "Foo"
       (testing "Ensure FK remappings are returned"
         (is (= [{:table_id   (mt/id :venues)
                  :id         (mt/id :venues :category_id)
diff --git a/test/metabase/http_client.clj b/test/metabase/http_client.clj
index 39e7d802ca53da23911d4ee944a9d2666f096ee0..5a92aac0030d2f286247534a059a9622ceb183e8 100644
--- a/test/metabase/http_client.clj
+++ b/test/metabase/http_client.clj
@@ -176,12 +176,19 @@
         request-fn  (method->request-fn method)
         url         (build-url url url-param-kwargs)
         method-name (str/upper-case (name method))
+        _           (log/debug method-name (pr-str url) (pr-str request-map))
         thunk       (fn []
                       (try
                         (request-fn url request-map)
                         (catch clojure.lang.ExceptionInfo e
                           (log/debug method-name url)
-                          (:object (ex-data e)))))
+                          (:object (ex-data e)))
+                        (catch Exception e
+                          (throw (ex-info (.getMessage e)
+                                          {:method  method-name
+                                           :url     url
+                                           :request request-map}
+                                          e)))))
         ;; if we expect a 4xx or 5xx status code then suppress and error messages that may be generated by the request.
         thunk       (if (and expected-status (>= expected-status 400))
                       (fn [] (tu.log/suppress-output (thunk)))
diff --git a/test/metabase/integrations/ldap_test.clj b/test/metabase/integrations/ldap_test.clj
index 67cac8b8db7d0605fbdb60844e13971c0ef39183..c7dbce0fd31f3c98680eabe35b8af382886c9498 100644
--- a/test/metabase/integrations/ldap_test.clj
+++ b/test/metabase/integrations/ldap_test.clj
@@ -1,8 +1,10 @@
 (ns metabase.integrations.ldap-test
   (:require [clojure.test :refer :all]
             [metabase.integrations.ldap :as ldap]
-            [metabase.test.integrations.ldap :as ldap.test]
-            [metabase.test.util :as tu]))
+            [metabase.integrations.ldap.default-implementation :as default-impl]
+            [metabase.public-settings.metastore :as metastore]
+            [metabase.test :as mt]
+            [metabase.test.integrations.ldap :as ldap.test]))
 
 (defn- get-ldap-details []
   {:host       "localhost"
@@ -17,97 +19,91 @@
 
 ;; The connection test should pass with valid settings
 (deftest connection-test
-  (testing "anonymous binds"
-    (testing "successfully connect to IPv4 host"
-      (is (= {:status :SUCCESS}
-             (ldap.test/with-ldap-server
-               (ldap/test-ldap-connection (get-ldap-details)))))))
-
-  (testing "invalid user search base"
-    (is (= :ERROR
-           (ldap.test/with-ldap-server
+  (ldap.test/with-ldap-server
+    (testing "anonymous binds"
+      (testing "successfully connect to IPv4 host"
+        (is (= {:status :SUCCESS}
+               (ldap/test-ldap-connection (get-ldap-details))))))
+
+    (testing "invalid user search base"
+      (is (= :ERROR
              (:status (ldap/test-ldap-connection (assoc (get-ldap-details)
-                                                        :user-base "dc=example,dc=com")))))))
-
-  (testing "invalid group search base"
-    (is (= :ERROR
-           (ldap.test/with-ldap-server
-             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :group-base "dc=example,dc=com")))))))
-
-  (testing "invalid bind DN"
-    (is (= :ERROR
-           (ldap.test/with-ldap-server
-             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :bind-dn "cn=Not Directory Manager")))))))
-
-  (testing "invalid bind password"
-    (is (= :ERROR
-           (ldap.test/with-ldap-server
-             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :password "wrong")))))))
-
-  (testing "basic get-connection works, will throw otherwise"
-    (is (= nil
-           (ldap.test/with-ldap-server
-             (.close (#'ldap/get-connection))))))
-
-  (testing "login should succeed"
-    (is (= true
-           (ldap.test/with-ldap-server
-             (ldap/verify-password "cn=Directory Manager" "password")))))
-
-  (testing "wrong password"
-    (is (= false
-           (ldap.test/with-ldap-server
-             (ldap/verify-password "cn=Directory Manager" "wrongpassword")))))
-
-  (testing "invalid DN fails"
-    (is (= false
-           (ldap.test/with-ldap-server
-             (ldap/verify-password "cn=Nobody,ou=nowhere,dc=metabase,dc=com" "password")))))
-
-  (testing "regular user login"
-    (is (= true
-           (ldap.test/with-ldap-server
-             (ldap/verify-password "cn=Sally Brown,ou=People,dc=metabase,dc=com" "1234")))))
-
-  (testing "fail regular user login with bad password"
-    (is (= false
-           (ldap.test/with-ldap-server
-             (ldap/verify-password "cn=Sally Brown,ou=People,dc=metabase,dc=com" "password")))))
-
-  (testing "find by username"
-    (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
-            :first-name "John"
-            :last-name  "Smith"
-            :email      "John.Smith@metabase.com"
-            :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
-           (ldap.test/with-ldap-server
-             (ldap/find-user "jsmith1")))))
-
-  (testing "find by email"
-    (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
-            :first-name "John"
-            :last-name  "Smith"
-            :email      "John.Smith@metabase.com"
-            :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
-           (ldap.test/with-ldap-server
-             (ldap/find-user "John.Smith@metabase.com")))))
-
-  (testing "find by email, no groups"
-    (is (= {:dn         "cn=Fred Taylor,ou=People,dc=metabase,dc=com"
-            :first-name "Fred"
-            :last-name  "Taylor"
-            :email      "fred.taylor@metabase.com"
-            :groups     []}
-           (ldap.test/with-ldap-server
-             (ldap/find-user "fred.taylor@metabase.com")))))
-
+                                                        :user-base "dc=example,dc=com"))))))
+
+    (testing "invalid group search base"
+      (is (= :ERROR
+             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :group-base "dc=example,dc=com"))))))
+
+    (testing "invalid bind DN"
+      (is (= :ERROR
+             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :bind-dn "cn=Not Directory Manager"))))))
+
+    (testing "invalid bind password"
+      (is (= :ERROR
+             (:status (ldap/test-ldap-connection (assoc (get-ldap-details) :password "wrong"))))))
+
+    (testing "basic get-connection works, will throw otherwise"
+      (is (= nil
+             (.close (#'ldap/get-connection)))))
+
+    (testing "login should succeed"
+      (is (= true
+             (ldap/verify-password "cn=Directory Manager" "password"))))
+
+    (testing "wrong password"
+      (is (= false
+             (ldap/verify-password "cn=Directory Manager" "wrongpassword"))))
+
+    (testing "invalid DN fails"
+      (is (= false
+             (ldap/verify-password "cn=Nobody,ou=nowhere,dc=metabase,dc=com" "password"))))
+
+    (testing "regular user login"
+      (is (= true
+             (ldap/verify-password "cn=Sally Brown,ou=People,dc=metabase,dc=com" "1234"))))
+
+    (testing "fail regular user login with bad password"
+      (is (= false
+             (ldap/verify-password "cn=Sally Brown,ou=People,dc=metabase,dc=com" "password"))))))
+
+(deftest find-test
+  ;; there are EE-specific versions of this test in `metabase-enterprise.enhancements.integrations.ldap-test`
+  (with-redefs [metastore/enable-enhancements? (constantly false)]
+    (ldap.test/with-ldap-server
+      (testing "find by username"
+        (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
+                :first-name "John"
+                :last-name  "Smith"
+                :email      "John.Smith@metabase.com"
+                :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
+               (ldap/find-user "jsmith1"))))
+
+      (testing "find by email"
+        (is (= {:dn         "cn=John Smith,ou=People,dc=metabase,dc=com"
+                :first-name "John"
+                :last-name  "Smith"
+                :email      "John.Smith@metabase.com"
+                :groups     ["cn=Accounting,ou=Groups,dc=metabase,dc=com"]}
+               (ldap/find-user "John.Smith@metabase.com"))))
+
+      (testing "find by email, no groups"
+        (is (= {:dn         "cn=Fred Taylor,ou=People,dc=metabase,dc=com"
+                :first-name "Fred"
+                :last-name  "Taylor"
+                :email      "fred.taylor@metabase.com"
+                :groups     []}
+               (ldap/find-user "fred.taylor@metabase.com")))))))
+
+(deftest group-matching-test
   (testing "LDAP group matching should identify Metabase groups using DN equality rules"
     (is (= #{1 2 3}
-           (tu/with-temporary-setting-values
+           (mt/with-temporary-setting-values
              [ldap-group-mappings {"cn=accounting,ou=groups,dc=metabase,dc=com" [1 2]
                                    "cn=shipping,ou=groups,dc=metabase,dc=com" [2 3]}]
-             (#'ldap/ldap-groups->mb-group-ids ["CN=Accounting,OU=Groups,DC=metabase,DC=com"
-                                                "CN=Shipping,OU=Groups,DC=metabase,DC=com"]))))))
+             (#'default-impl/ldap-groups->mb-group-ids
+              ["CN=Accounting,OU=Groups,DC=metabase,DC=com"
+               "CN=Shipping,OU=Groups,DC=metabase,DC=com"]
+              {:group-mappings (ldap/ldap-group-mappings)}))))))
 
 ;; For hosts that do not support IPv6, the connection code will return an error
 ;; This isn't a failure of the code, it's a failure of the host.
diff --git a/test/metabase/metabot/command_test.clj b/test/metabase/metabot/command_test.clj
index d4adcdfdd987d75c46adc59a9a3e122119fcf8d1..d2146da74de3cce2342293d80076ad9938a838e7 100644
--- a/test/metabase/metabot/command_test.clj
+++ b/test/metabase/metabot/command_test.clj
@@ -56,7 +56,7 @@
         (is (= {:response '(Exception. "Card Cam's Cool MetaBot Card not found.")
                 :messages []}
                (command "show" "Cam's Cool MetaBot Card")))
-        (is (re= #"You don't have any cards yet"
+        (is (re= #"You don't have any cards yet\."
                  (metabot.cmd/command "list"))))))
   (testing "with ambiguous matches"
     (mt/with-temp* [Card [{card-1-id :id} {:dataset_query (venues-count-query), :name "Cam's Cool MetaBot Card 1"}]
diff --git a/test/metabase/middleware/security_test.clj b/test/metabase/middleware/security_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..bd34fb275e2a92d92bf7feef3f1883718efbdca3
--- /dev/null
+++ b/test/metabase/middleware/security_test.clj
@@ -0,0 +1,51 @@
+(ns metabase.middleware.security-test
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
+            [metabase.middleware.security :as mw.security]
+            [metabase.test.util :as tu]))
+
+(defn- csp-frame-ancestors-directive
+  []
+  (-> (mw.security/security-headers)
+      (get "Content-Security-Policy")
+      (str/split #"; *")
+      (as-> xs (filter #(str/starts-with? % "frame-ancestors ") xs))
+      first))
+
+(defn- x-frame-options-header
+  []
+  (get (mw.security/security-headers) "X-Frame-Options"))
+
+(deftest csp-header-frame-ancestor-tests
+  (testing "Frame ancestors from `embedding-app-origin` setting"
+    (let [multiple-ancestors "https://*.metabase.com http://metabase.internal"]
+      (tu/with-temporary-setting-values [enable-embedding     true
+                                         embedding-app-origin multiple-ancestors]
+        (is (= (str "frame-ancestors " multiple-ancestors)
+               (csp-frame-ancestors-directive))))))
+
+  (testing "Frame ancestors is 'none' for nil `embedding-app-origin`"
+    (tu/with-temporary-setting-values [enable-embedding     true
+                                       embedding-app-origin nil]
+      (is (= "frame-ancestors 'none'"
+             (csp-frame-ancestors-directive)))))
+
+  (testing "Frame ancestors is 'none' if embedding is disabled"
+    (tu/with-temporary-setting-values [enable-embedding     false
+                                       embedding-app-origin "https: http:"]
+      (is (= "frame-ancestors 'none'"
+          (csp-frame-ancestors-directive))))))
+
+(deftest xframeoptions-header-tests
+  (testing "`DENY` when embedding is disabled"
+    (tu/with-temporary-setting-values [enable-embedding     false
+                                       embedding-app-origin "https://somesite.metabase.com"]
+      (is (= "DENY" (x-frame-options-header)))))
+
+  (testing "Only the first of multiple embedding origins are used in `X-Frame-Options`"
+    (let [embedding-app-origins ["https://site1.metabase.com" "https://our_metabase.internal"]]
+      (tu/with-temporary-setting-values [enable-embedding     true
+                                         embedding-app-origin (str/join " " embedding-app-origins)]
+        (is (= (str "ALLOW-FROM " (first embedding-app-origins))
+               (x-frame-options-header)))))))
diff --git a/test/metabase/middleware/session_test.clj b/test/metabase/middleware/session_test.clj
index 9d39595b131ffaf51604dcd6c960eb5b48e6bd7c..f18c2aa3d0cd769c4899e4b8885092ca489b15ac 100644
--- a/test/metabase/middleware/session_test.clj
+++ b/test/metabase/middleware/session_test.clj
@@ -1,34 +1,62 @@
 (ns metabase.middleware.session-test
-  (:require [clojure.test :refer :all]
+  (:require [clojure
+             [string :as str]
+             [test :refer :all]]
             [environ.core :as env]
             [expectations :refer [expect]]
             [metabase
+             [config :as config]
              [db :as mdb]
              [models :refer [Session User]]
              [test :as mt]]
             [metabase.api.common :refer [*current-user* *current-user-id*]]
+            [metabase.core.initialization-status :as init-status]
             [metabase.driver.sql.query-processor :as sql.qp]
             [metabase.middleware.session :as mw.session]
             [metabase.test.data.users :as test-users]
             [metabase.util.i18n :as i18n]
             [ring.mock.request :as mock]
-            [toucan.db :as db])
-  (:import java.util.UUID))
+            [toucan.db :as db]
+            [toucan.util.test :as tt])
+  (:import clojure.lang.ExceptionInfo
+           java.util.UUID))
+
+(use-fixtures :once (fn [thunk]
+                      (init-status/set-complete!)
+                      (thunk)))
+
+(def ^:private session-cookie @#'mw.session/metabase-session-cookie)
+
+(def ^:private test-uuid #uuid "092797dd-a82a-4748-b393-697d7bb9ab65")
+
+(deftest session-cookie-test
+  (testing "`SameSite` value is read from config (env)"
+    (is (= :lax ; Default value
+           (with-redefs [env/env (dissoc env/env :mb-session-cookie-samesite)]
+             (#'config/mb-session-cookie-samesite*))))
+
+    (is (= :strict
+           (with-redefs [env/env (assoc env/env :mb-session-cookie-samesite "StRiCt")]
+             (#'config/mb-session-cookie-samesite*))))
+
+    (is (= :none
+           (with-redefs [env/env (assoc env/env :mb-session-cookie-samesite "NONE")]
+             (#'config/mb-session-cookie-samesite*))))
+
+    (is (thrown-with-msg? ExceptionInfo #"Invalid value for MB_COOKIE_SAMESITE"
+          (with-redefs [env/env (assoc env/env :mb-session-cookie-samesite "invalid value")]
+            (#'config/mb-session-cookie-samesite*))))))
 
 (deftest set-session-cookie-test
   (let [uuid (UUID/randomUUID)]
     (testing "should unset the old SESSION_ID if it's present"
-      (is (= {"metabase.SESSION_ID"
-              {:value   nil
-               :expires "Thu, 1 Jan 1970 00:00:00 GMT"
-               :path    "/"}
-              "metabase.SESSION"
+      (is (= {"metabase.SESSION"
               {:value     (str uuid)
                :same-site :lax
                :http-only true
                :path      "/"
                :max-age   1209600}}
-             (-> (mw.session/set-session-cookie {} {} uuid)
+             (-> (mw.session/set-session-cookie {} {} {:id uuid, :type :normal})
                  :cookies))))
     (testing "if `MB_SESSION_COOKIES=true` we shouldn't set a `Max-Age`"
       (is (= {:value     (str uuid)
@@ -37,31 +65,32 @@
               :path      "/"}
              (let [env env/env]
                (with-redefs [env/env (assoc env :mb-session-cookies "true")]
-                 (-> (mw.session/set-session-cookie {} {} uuid)
+                 (-> (mw.session/set-session-cookie {} {} {:id uuid, :type :normal})
                      (get-in [:cookies "metabase.SESSION"])))))))))
 
 ;; if request is an HTTPS request then we should set `:secure true`. There are several different headers we check for
 ;; this. Make sure they all work.
 (deftest secure-cookie-test
-  (doseq [[headers expected] [[{"x-forwarded-proto" "https"} true]
-                              [{"x-forwarded-proto" "http"} false]
+  (doseq [[headers expected] [[{"x-forwarded-proto" "https"}    true]
+                              [{"x-forwarded-proto" "http"}     false]
                               [{"x-forwarded-protocol" "https"} true]
-                              [{"x-forwarded-protocol" "http"} false]
-                              [{"x-url-scheme" "https"} true]
-                              [{"x-url-scheme" "http"} false]
-                              [{"x-forwarded-ssl" "on"} true]
-                              [{"x-forwarded-ssl" "off"} false]
-                              [{"front-end-https" "on"} true]
-                              [{"front-end-https" "off"} false]
-                              [{"origin" "https://mysite.com"} true]
-                              [{"origin" "http://mysite.com"} false]]]
-    (let [actual (-> (mw.session/set-session-cookie {:headers headers} {} (UUID/randomUUID))
-                     (get-in [:cookies "metabase.SESSION" :secure])
-                     boolean)]
-      (is (= expected
-             actual)
-          (format "With headers %s we %s set the 'secure' attribute on the session cookie"
-                  (pr-str headers) (if expected "SHOULD" "SHOULD NOT"))))))
+                              [{"x-forwarded-protocol" "http"}  false]
+                              [{"x-url-scheme" "https"}         true]
+                              [{"x-url-scheme" "http"}          false]
+                              [{"x-forwarded-ssl" "on"}         true]
+                              [{"x-forwarded-ssl" "off"}        false]
+                              [{"front-end-https" "on"}         true]
+                              [{"front-end-https" "off"}        false]
+                              [{"origin" "https://mysite.com"}  true]
+                              [{"origin" "http://mysite.com"}   false]]]
+    (testing (format "With headers %s we %s set the 'secure' attribute on the session cookie"
+                     (pr-str headers) (if expected "SHOULD" "SHOULD NOT"))
+      (let [session {:id   (UUID/randomUUID)
+                     :type :normal}
+            actual  (-> (mw.session/set-session-cookie {:headers headers} {} session)
+                        (get-in [:cookies "metabase.SESSION" :secure])
+                        boolean)]
+        (is (= expected actual))))))
 
 (deftest session-expired-test
   (testing "Session expiration time = 1 minute"
@@ -75,15 +104,41 @@
           (mt/with-temp User [{user-id :id}]
             (let [session-id (str (UUID/randomUUID))]
               (db/simple-insert! Session {:id session-id, :user_id user-id, :created_at created-at})
-              (let [session (#'mw.session/current-user-info-for-session session-id)]
+              (let [session (#'mw.session/current-user-info-for-session session-id nil)]
                 (if expected
                   (is (= nil
                          session))
                   (is (some? session)))))))))))
 
 
+;;; ------------------------------------- tests for full-app embedding sessions --------------------------------------
+
+(def ^:private embedded-session-cookie @#'mw.session/metabase-embedded-session-cookie)
+(def ^:private anti-csrf-token-header @#'mw.session/anti-csrf-token-header)
+
+(def ^:private test-anti-csrf-token "84482ddf1bb178186ed9e1c0b1e05a2d")
+
+(def ^:private test-full-app-embed-session
+  {:id               test-uuid
+   :anti_csrf_token  test-anti-csrf-token
+   :type             :full-app-embed})
+
+;; test that we can set a full-app-embedding session cookie
+(expect
+  {:body    {}
+   :status  200
+   :cookies {embedded-session-cookie
+             {:value     "092797dd-a82a-4748-b393-697d7bb9ab65"
+              :http-only true
+              :path      "/"}}
+   :headers {anti-csrf-token-header test-anti-csrf-token}}
+  (mw.session/set-session-cookie {} {} test-full-app-embed-session))
+
+
 ;;; ---------------------------------------- TEST wrap-session-id middleware -----------------------------------------
 
+(def ^:private session-header @#'mw.session/metabase-session-header)
+
 ;; create a simple example of our middleware wrapped around a handler that simply returns the request
 ;; this works in this case because the only impact our middleware has is on the request
 (defn- wrapped-handler [request]
@@ -106,7 +161,7 @@
   "foobar"
   (:metabase-session-id
    (wrapped-handler
-    (mock/header (mock/request :get "/anyurl") @#'mw.session/metabase-session-header "foobar"))))
+    (mock/header (mock/request :get "/anyurl") session-header "foobar"))))
 
 
 ;; extract session-id from cookie
@@ -115,7 +170,7 @@
   (:metabase-session-id
    (wrapped-handler
     (assoc (mock/request :get "/anyurl")
-      :cookies {@#'mw.session/metabase-session-cookie {:value "cookie-session"}}))))
+      :cookies {session-cookie {:value "cookie-session"}}))))
 
 
 ;; if both header and cookie session-ids exist, then we expect the cookie to take precedence
@@ -123,11 +178,96 @@
   "cookie-session"
   (:metabase-session-id
    (wrapped-handler
-    (assoc (mock/header (mock/request :get "/anyurl") @#'mw.session/metabase-session-header "foobar")
-      :cookies {@#'mw.session/metabase-session-cookie {:value "cookie-session"}}))))
+    (assoc (mock/header (mock/request :get "/anyurl") session-header "foobar")
+           :cookies {session-cookie {:value "cookie-session"}}))))
+
+;; `wrap-session-id` should handle anti-csrf headers they way we'd expect
+(expect
+  {:anti-csrf-token     "84482ddf1bb178186ed9e1c0b1e05a2d"
+   :cookies             {embedded-session-cookie {:value "092797dd-a82a-4748-b393-697d7bb9ab65"}}
+   :metabase-session-id "092797dd-a82a-4748-b393-697d7bb9ab65"
+   :uri                 "/anyurl"}
+  (let [request (-> (mock/request :get "/anyurl")
+                    (assoc :cookies {embedded-session-cookie {:value (str test-uuid)}})
+                    (assoc-in [:headers anti-csrf-token-header] test-anti-csrf-token))]
+    (select-keys (wrapped-handler request) [:anti-csrf-token :cookies :metabase-session-id :uri])))
+
+(deftest current-user-info-for-session-test
+  (testing "make sure the `current-user-info-for-session` logic is working correctly"
+    ;; for some reason Toucan seems to be busted with models with non-integer IDs and `with-temp` doesn't seem to work
+    ;; the way we'd expect :/
+    (try
+      (tt/with-temp Session [session {:id (str test-uuid), :user_id (test-users/user->id :lucky)}]
+        (is (= {:metabase-user-id (test-users/user->id :lucky), :is-superuser? false, :user-locale nil}
+               (#'mw.session/current-user-info-for-session (str test-uuid) nil))))
+      (finally
+        (db/delete! Session :id (str test-uuid)))))
+
+  (testing "superusers should come back as `:is-superuser?`"
+    (try
+      (tt/with-temp Session [session {:id (str test-uuid), :user_id (test-users/user->id :crowberto)}]
+        (is (= {:metabase-user-id (test-users/user->id :crowberto), :is-superuser? true, :user-locale nil}
+               (#'mw.session/current-user-info-for-session (str test-uuid) nil))))
+      (finally
+        (db/delete! Session :id (str test-uuid)))))
+
+  (testing "full-app-embed sessions shouldn't come back if we don't explicitly specifiy the anti-csrf token"
+    (try
+      (tt/with-temp Session [session {:id              (str test-uuid)
+                                      :user_id         (test-users/user->id :lucky)
+                                      :anti_csrf_token test-anti-csrf-token}]
+        (is (= nil
+               (#'mw.session/current-user-info-for-session (str test-uuid) nil))))
+      (finally
+        (db/delete! Session :id (str test-uuid))))
+
+    (testing "...but if we do specifiy the token, they should come back"
+      (try
+        (tt/with-temp Session [session {:id              (str test-uuid)
+                                        :user_id         (test-users/user->id :lucky)
+                                        :anti_csrf_token test-anti-csrf-token}]
+          (is (= {:metabase-user-id (test-users/user->id :lucky), :is-superuser? false, :user-locale nil}
+                 (#'mw.session/current-user-info-for-session (str test-uuid) test-anti-csrf-token))))
+        (finally
+          (db/delete! Session :id (str test-uuid))))
+
+      (testing "(unless the token is wrong)"
+        (try
+          (tt/with-temp Session [session {:id              (str test-uuid)
+                                          :user_id         (test-users/user->id :lucky)
+                                          :anti_csrf_token test-anti-csrf-token}]
+            (is (= nil
+                   (#'mw.session/current-user-info-for-session (str test-uuid) (str/join (reverse test-anti-csrf-token))))))
+          (finally
+            (db/delete! Session :id (str test-uuid)))))))
+
+  (testing "if we specify an anti-csrf token we shouldn't get back a session without that token"
+    (try
+      (tt/with-temp Session [session {:id      (str test-uuid)
+                                      :user_id (test-users/user->id :lucky)}]
+        (is (= nil
+               (#'mw.session/current-user-info-for-session (str test-uuid) test-anti-csrf-token))))
+      (finally
+        (db/delete! Session :id (str test-uuid)))))
 
+  (testing "shouldn't fetch expired sessions"
+    (try
+      (tt/with-temp Session [session {:id      (str test-uuid)
+                                      :user_id (test-users/user->id :lucky)}]
+        ;; use low-level `execute!` because updating is normally disallowed for Sessions
+        (db/execute! {:update Session, :set {:created_at (java.sql.Date. 0)}, :where [:= :id (str test-uuid)]})
+        (is (= nil
+               (#'mw.session/current-user-info-for-session (str test-uuid) nil))))
+      (finally
+        (db/delete! Session :id (str test-uuid)))))
 
-;;; --------------------------------------- TEST bind-current-user middleware ----------------------------------------
+  (testing "shouldn't fetch sessions for inactive users"
+    (try
+      (tt/with-temp Session [session {:id (str test-uuid), :user_id (test-users/user->id :trashbird)}]
+        (is (= nil
+               (#'mw.session/current-user-info-for-session (str test-uuid) nil))))
+      (finally
+        (db/delete! Session :id (str test-uuid))))))
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns our bound variables for users
 (defn- user-bound-handler [request]
diff --git a/test/metabase/middleware/util_test.clj b/test/metabase/middleware/util_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fe3debb2358f94bf10c723131b40dc8d0ec9e15a
--- /dev/null
+++ b/test/metabase/middleware/util_test.clj
@@ -0,0 +1,24 @@
+(ns metabase.middleware.util-test
+  (:require [expectations :refer [expect]]
+            [metabase.middleware.util :as mw.util]))
+
+(defn- https? [headers]
+  (mw.util/https-request? {:headers headers}))
+
+(expect true  (https? {"x-forwarded-proto" "https"}))
+(expect false (https? {"x-forwarded-proto" "http"}))
+
+(expect true  (https? {"x-forwarded-protocol" "https"}))
+(expect false (https? {"x-forwarded-protocol" "http"}))
+
+(expect true  (https? {"x-url-scheme" "https"}))
+(expect false (https? {"x-url-scheme" "http"}))
+
+(expect true  (https? {"x-forwarded-ssl" "on"}))
+(expect false (https? {"x-forwarded-ssl" "off"}))
+
+(expect true  (https? {"front-end-https" "on"}))
+(expect false (https? {"front-end-https" "off"}))
+
+(expect true  (https? {"origin" "https://mysite.com"}))
+(expect false (https? {"origin" "http://mysite.com"}))
diff --git a/test/metabase/models/collection/graph_test.clj b/test/metabase/models/collection/graph_test.clj
index 3f3fe161ed76d4d16d8dcd6cc5ecc673150185cd..17cb9a66ef3125bc7e4bc0e54c9df9db5b3731ae 100644
--- a/test/metabase/models/collection/graph_test.clj
+++ b/test/metabase/models/collection/graph_test.clj
@@ -28,7 +28,7 @@
    (replace-collection-ids collection-or-id graph :COLLECTION))
 
   ([collection-or-id graph replacement-key]
-   (let [id      (u/get-id collection-or-id)
+   (let [id      (if (map? collection-or-id) (:id collection-or-id) collection-or-id)
          ;; match variations that pop up depending on whether the map was serialized to JSON. 100, :100, or "100"
          id-keys #{id (str id) (keyword (str id))}]
      (update graph :groups (partial m/map-vals (partial m/map-keys (fn [collection-id]
@@ -322,7 +322,7 @@
                      (fn
                        ([graph]
                         (-> (get-in graph [:groups group-id])
-                            (select-keys (vals id->alias))))
+                            (select-keys (cons :root (vals id->alias)))))
                        ([graph [collection-id k]]
                         (replace-collection-ids collection-id graph k)))
                      graph
@@ -330,33 +330,29 @@
           (doseq [collection [default-a default-ab currency-a currency-ab]]
             (perms/grant-collection-read-permissions! group-id collection))
           (testing "Calling (graph) with no args should only show Collections in the default namespace"
-            (is (= {"Default A" :read, "Default A -> B" :read}
+            (is (= {"Default A" :read, "Default A -> B" :read, :root :none}
                    (nice-graph (graph/graph))
                    (nice-graph (graph/graph nil)))))
 
           (testing "You should be able to pass an different namespace to (graph) to see Collections in that namespace"
-            (is (= {"Currency A" :read, "Currency A -> B" :read}
+            (is (= {"Currency A" :read, "Currency A -> B" :read, :root :none}
                    (nice-graph (graph/graph :currency)))))
 
-          (testing "Should be able to pass `::graph/all` to get a combined graph of all namespaces (used for saving revisions)"
-            (is (= {"Default A" :read, "Default A -> B" :read "Currency A" :read, "Currency A -> B" :read}
-                   (nice-graph (graph/graph ::graph/all)))))
-
           ;; bind a current user so CollectionRevisions get saved.
           (mt/with-test-user :crowberto
             (testing "Should be able to update the graph for the default namespace.\n"
-              (let [before (graph/graph ::graph/all)]
+              (let [before (graph/graph)]
                 (graph/update-graph! (assoc (graph/graph) :groups {group-id {default-ab :write, currency-ab :write}}))
-                (is (= {"Default A" :read, "Default A -> B" :write}
+                (is (= {"Default A" :read, "Default A -> B" :write, :root :none}
                        (nice-graph (graph/graph))))
 
                 (testing "Updates to Collections in other namespaces should be ignored"
-                  (is (= {"Currency A" :read, "Currency A -> B" :read}
+                  (is (= {"Currency A" :read, "Currency A -> B" :read, :root :none}
                          (nice-graph (graph/graph :currency)))))
 
                 (testing "A CollectionRevision recording the *changes* to the perms graph should be saved."
                   (is (schema= {:id         su/IntGreaterThanZero
-                                :before     (s/eq (mt/obj->json->obj before))
+                                :before     (s/eq (mt/obj->json->obj (assoc before :namespace nil)))
                                 :after      (s/eq {(keyword (str group-id)) {(keyword (str default-ab)) "write"}})
                                 :user_id    (s/eq (mt/user->id :crowberto))
                                 :created_at java.time.temporal.Temporal
@@ -364,20 +360,58 @@
                                (db/select-one CollectionRevision {:order-by [[:id :desc]]}))))))
 
             (testing "Should be able to update the graph for a non-default namespace.\n"
-              (let [before (graph/graph ::graph/all)]
+              (let [before (graph/graph :currency)]
                 (graph/update-graph! :currency (assoc (graph/graph) :groups {group-id {default-a :write, currency-a :write}}))
-                (is (= {"Currency A" :write, "Currency A -> B" :read}
+                (is (= {"Currency A" :write, "Currency A -> B" :read, :root :none}
                        (nice-graph (graph/graph :currency))))
 
                 (testing "Updates to Collections in other namespaces should be ignored"
-                  (is (= {"Default A" :read, "Default A -> B" :write}
+                  (is (= {"Default A" :read, "Default A -> B" :write, :root :none}
                          (nice-graph (graph/graph)))))
 
                 (testing "A CollectionRevision recording the *changes* to the perms graph should be saved."
                   (is (schema= {:id         su/IntGreaterThanZero
-                                :before     (s/eq (mt/obj->json->obj before))
+                                :before     (s/eq (mt/obj->json->obj (assoc before :namespace "currency")))
                                 :after      (s/eq {(keyword (str group-id)) {(keyword (str currency-a)) "write"}})
                                 :user_id    (s/eq (mt/user->id :crowberto))
                                 :created_at java.time.temporal.Temporal
                                 s/Keyword   s/Any}
-                               (db/select-one CollectionRevision {:order-by [[:id :desc]]}))))))))))))
+                               (db/select-one CollectionRevision {:order-by [[:id :desc]]}))))))
+
+            (testing "should be able to update permissions for the Root Collection in the default namespace via the graph"
+              (graph/update-graph! (assoc (graph/graph) :groups {group-id {:root :read}}))
+              (is (= {:root :read, "Default A" :read, "Default A -> B" :write}
+                     (nice-graph (graph/graph))))
+
+              (testing "\nshouldn't affect Root Collection perms for non-default namespaces"
+                (is (= {:root :none, "Currency A" :write, "Currency A -> B" :read}
+                       (nice-graph (graph/graph :currency)))))
+
+              (testing "A CollectionRevision recording the *changes* to the perms graph should be saved."
+                (is (schema= {:before   {:namespace (s/eq nil)
+                                         :groups    {(keyword (str group-id)) {:root     (s/eq "none")
+                                                                               s/Keyword s/Any}
+                                                     s/Keyword                s/Any}
+                                         s/Keyword  s/Any}
+                              :after    {(keyword (str group-id)) {:root (s/eq "read")}}
+                              s/Keyword s/Any}
+                             (db/select-one CollectionRevision {:order-by [[:id :desc]]})))))
+
+            (testing "should be able to update permissions for Root Collection in non-default namespace"
+              (graph/update-graph! :currency (assoc (graph/graph :currency) :groups {group-id {:root :write}}))
+              (is (= {:root :write, "Currency A" :write, "Currency A -> B" :read}
+                     (nice-graph (graph/graph :currency))))
+
+              (testing "\nshouldn't affect Root Collection perms for default namespace"
+                (is (= {:root :read, "Default A" :read, "Default A -> B" :write}
+                       (nice-graph (graph/graph)))))
+
+              (testing "A CollectionRevision recording the *changes* to the perms graph should be saved."
+                (is (schema= {:before   {:namespace (s/eq "currency")
+                                         :groups    {(keyword (str group-id)) {:root     (s/eq "none")
+                                                                               s/Keyword s/Any}
+                                                     s/Keyword                s/Any}
+                                         s/Keyword  s/Any}
+                              :after    {(keyword (str group-id)) {:root (s/eq "write")}}
+                              s/Keyword s/Any}
+                             (db/select-one CollectionRevision {:order-by [[:id :desc]]})))))))))))
diff --git a/test/metabase/models/dashboard_test.clj b/test/metabase/models/dashboard_test.clj
index 9b05fbac1b0142fc26c7093efa6e24c91d694df3..f49d72afc2a2b4903ba02226bc65e3fd47d22ff8 100644
--- a/test/metabase/models/dashboard_test.clj
+++ b/test/metabase/models/dashboard_test.clj
@@ -250,3 +250,17 @@
              clojure.lang.ExceptionInfo
              #"A Dashboard can only go in Collections in the \"default\" namespace"
              (db/update! Dashboard card-id {:collection_id collection-id})))))))
+
+(deftest validate-parameters-test
+  (testing "Should validate Dashboard :parameters when"
+    (testing "creating"
+      (is (thrown-with-msg?
+           clojure.lang.ExceptionInfo
+           #":parameters must be a sequence of maps with String :id keys"
+           (mt/with-temp Dashboard [_ {:parameters {:a :b}}]))))
+    (testing "updating"
+      (mt/with-temp Dashboard [{:keys [id]} {:parameters []}]
+        (is (thrown-with-msg?
+             clojure.lang.ExceptionInfo
+             #":parameters must be a sequence of maps with String :id keys"
+             (db/update! Dashboard id :parameters [{:id 100}])))))))
diff --git a/test/metabase/models/field_values_test.clj b/test/metabase/models/field_values_test.clj
index 2cf8f538921714c2e03dbef6ed639dd0d9320689..95b0bab84b502d0a1742fbeea3004cfbccd0c9be 100644
--- a/test/metabase/models/field_values_test.clj
+++ b/test/metabase/models/field_values_test.clj
@@ -1,6 +1,7 @@
 (ns metabase.models.field-values-test
   "Tests for specific behavior related to FieldValues and functions in the `metabase.models.field-values` namespace."
-  (:require [clojure
+  (:require [cheshire.core :as json]
+            [clojure
              [string :as str]
              [test :refer :all]]
             [clojure.java.jdbc :as jdbc]
@@ -121,6 +122,32 @@
   (sync/sync-database! db)
   (find-values field-values-id))
 
+(deftest values-less-than-total-max-length?-test
+  (testing "values-less-than-total-max-length?"
+    (with-redefs [field-values/total-max-length 10]
+      (is (= true
+             (#'field-values/values-less-than-total-max-length? ["a" "b" "c"])))
+      (is (= false
+             (#'field-values/values-less-than-total-max-length? ["123" "4567" "8901"])))
+      (testing "Should only consume enough values to determine whether length is over limit"
+        (let [realized? (atom false)
+              vs        (lazy-cat ["123" "4567" "8901" "2345"] (do (reset! realized? true) ["Shouldn't get here"]))]
+          (is (= false
+                 (#'field-values/values-less-than-total-max-length? vs)))
+          (testing "Entire lazy seq shouldn't be realized"
+            (is (= false
+                   @realized?))))))))
+
+(deftest normalize-human-readable-values-test
+  (testing "If FieldValues were saved as a map, normalize them to a sequence on the way out"
+    (mt/with-temp FieldValues [fv {:field_id (mt/id :venues :id)
+                                   :values   (json/generate-string ["1" "2" "3"])}]
+      (db/execute! {:update FieldValues
+                    :set    {:human_readable_values (json/generate-string {"1" "a", "2" "b", "3" "c"})}
+                    :where  [:= :id (:id fv)]})
+      (is (= ["a" "b" "c"]
+             (:human_readable_values (FieldValues (:id fv))))))))
+
 (deftest update-human-readable-values-test
   (testing "Test \"fixing\" of human readable values when field values change"
     (binding [mdb/*allow-potentailly-unsafe-connections* true]
@@ -142,7 +169,7 @@
                 field-id        (db/select-one-field :id Field :table_id table-id :name "CATEGORY_ID")
                 field-values-id (db/select-one-field :id FieldValues :field_id field-id)]
             ;; Add in human readable values for remapping
-            (db/update! FieldValues field-values-id {:human_readable_values "[\"a\",\"b\",\"c\"]"})
+            (db/update! FieldValues field-values-id {:human_readable_values ["a" "b" "c"]})
             (let [expected-original-values {:values                [1 2 3]
                                             :human_readable_values ["a" "b" "c"]}
                   expected-updated-values  {:values                [-2 -1 0 1 2 3]
@@ -170,3 +197,17 @@
                 (jdbc/delete! conn :foo ["id in (?,?,?)" 1 2 3])
                 (is (= {:values [-2 -1 0] :human_readable_values ["-2" "-1" "0"]}
                        (sync-and-find-values db field-values-id)))))))))))
+
+(deftest validate-human-readable-values-test
+  (testing "Should validate FieldValues :human_readable_values when"
+    (testing "creating"
+      (is (thrown-with-msg?
+           clojure.lang.ExceptionInfo
+           #"Invalid human-readable-values"
+           (mt/with-temp FieldValues [_ {:field_id (mt/id :venues :id), :human_readable_values {"1" "A", "2", "B"}}]))))
+    (testing "updating"
+      (mt/with-temp FieldValues [{:keys [id]} {:field_id (mt/id :venues :id), :human_readable_values []}]
+        (is (thrown-with-msg?
+             clojure.lang.ExceptionInfo
+             #"Invalid human-readable-values"
+             (db/update! FieldValues id :human_readable_values {"1" "A", "2", "B"})))))))
diff --git a/test/metabase/models/session_test.clj b/test/metabase/models/session_test.clj
index 24582ee5899d87f307bd5f91e8506496c455c6a3..64b9838ef7729356673b345cd1ba68d3bfdacf76 100644
--- a/test/metabase/models/session_test.clj
+++ b/test/metabase/models/session_test.clj
@@ -1,31 +1,37 @@
 (ns metabase.models.session-test
-  (:require [clojure.test :refer :all]
-            [metabase.models :refer [Session User]]
-            [metabase.models.session :as session]
-            [metabase.test.util :as tu]
-            [toucan.db :as db]
-            [toucan.util.test :as tt]))
+  (:require [expectations :refer [expect]]
+            [metabase.middleware.misc :as mw.misc]
+            [metabase.models.session :as session :refer [Session]]
+            [metabase.test.data.users :as test-users]
+            [toucan
+             [db :as db]
+             [models :as t.models]]))
 
-(deftest first-session-for-user-test
-  (tt/with-temp User [{user-id :id} {:first_name (tu/random-name)
-                                     :last_name  (tu/random-name)
-                                     :email      (tu/random-email)
-                                     :password   "nada"}]
-    (db/simple-insert-many! Session
-      [{:id         "the-greatest-day-ever"
-        :user_id    user-id
-        :created_at #t "1980-10-19T05:05:05.000Z"}
-       {:id         "even-more-greatness"
-        :user_id    user-id
-        :created_at #t "1980-10-19T05:08:05.000Z"}
-       {:id         "the-world-of-bi-changes-forever"
-        :user_id    user-id
-        :created_at #t "2015-10-21"}
-       {:id         "something-could-have-happened"
-        :user_id    user-id
-        :created_at #t "1999-12-31"}
-       {:id         "now"
-        :user_id    user-id
-        :created_at :%now}])
-    (is (= "the-greatest-day-ever"
-           (session/first-session-for-user user-id)))))
+(def ^:private test-uuid #uuid "092797dd-a82a-4748-b393-697d7bb9ab65")
+
+  ;; for some reason Toucan seems to be busted with models with non-integer IDs and `with-temp` doesn't seem to work
+  ;; the way we'd expect :/
+(defn- new-session []
+  (try
+    (db/insert! Session {:id (str test-uuid), :user_id (test-users/user->id :trashbird)})
+    (-> (Session (str test-uuid)) t.models/post-insert (dissoc :created_at))
+    (finally
+      (db/delete! Session :id (str test-uuid)))))
+
+;; when creating a new Session, it should come back with an added `:type` key
+(expect
+  {:id              "092797dd-a82a-4748-b393-697d7bb9ab65"
+   :user_id         (test-users/user->id :trashbird)
+   :anti_csrf_token nil
+   :type            :normal}
+  (new-session))
+
+;; if request is an embedding request, we should get ourselves an embedded Session
+(expect
+  {:id              "092797dd-a82a-4748-b393-697d7bb9ab65"
+   :user_id         (test-users/user->id :trashbird)
+   :anti_csrf_token "315c1279c6f9f873bf1face7afeee420"
+   :type            :full-app-embed}
+  (binding [mw.misc/*request* {:headers {"x-metabase-embedded" "true"}}]
+    (with-redefs [session/random-anti-csrf-token (constantly "315c1279c6f9f873bf1face7afeee420")]
+      (new-session))))
diff --git a/test/metabase/public_settings/metastore_test.clj b/test/metabase/public_settings/metastore_test.clj
index 88b445c3ccd92ef7adc01285c02b52dd1b23f1a8..dcb14257ee2e3b5e479e8d112395ef1094bd0665 100644
--- a/test/metabase/public_settings/metastore_test.clj
+++ b/test/metabase/public_settings/metastore_test.clj
@@ -1,10 +1,19 @@
 (ns metabase.public-settings.metastore-test
-  (:require [metabase.public-settings.metastore :as metastore]))
+  (:require [cheshire.core :as json]
+            [clj-http.fake :as http-fake]
+            [clojure.test :refer :all]
+            [metabase.models.user :refer [User]]
+            [metabase.public-settings.metastore :as metastore]
+            [toucan.util.test :as tt]))
 
 (defn do-with-metastore-token-features [features f]
-  (with-redefs [metastore/token-features (constantly (set (map name features)))]
-    (f)))
+  (let [features (set (map name features))]
+    (testing (format "\nWith EE token features = %s" (pr-str features))
+      (with-redefs [metastore/token-features (constantly features)]
+        (f)))))
 
+;; TODO -- move this to a shared `metabase-enterprise.test` namespace. Consider adding logic that will alias stuff in
+;; `metabase-enterprise.test` in `metabase.test` as well *if* EE code is available
 (defmacro with-metastore-token-features
   "Execute `body` with the allowed premium features for the MetaStore token set to `features`. Intended for use testing
   feature-flagging.
@@ -15,3 +24,30 @@
   {:style/indent 1}
   [features & body]
   `(do-with-metastore-token-features ~features (fn [] ~@body)))
+
+(defn- token-status-response
+  [token metastore-response]
+  (http-fake/with-fake-routes-in-isolation
+    {{:address      (#'metastore/token-status-url token)
+      :query-params {:users (str (#'metastore/active-user-count))}}
+     (constantly metastore-response)}
+    (#'metastore/fetch-token-status* token)))
+
+(def ^:private token-response-fixture
+  (json/encode {:valid    true
+                :status   "fake"
+                :features ["test" "fixture"]
+                :trial    false}))
+
+(deftest fetch-token-status-test
+  (tt/with-temp User [user {:email "admin@example.com"}]
+    (let [token "fa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3ebfa3e"]
+      (testing "With the backend unavailable"
+        (let [result (token-status-response token {:status 500})]
+          (is (false? (:valid result)))))
+
+      (testing "With a valid token"
+        (let [result (token-status-response token {:status 200
+                                                   :body   token-response-fixture})]
+          (is (:valid result))
+          (is (contains? (set (:features result)) "test")))))))
diff --git a/test/metabase/pulse_test.clj b/test/metabase/pulse_test.clj
index f88e5d5f76480c553ca4d249b18ecc3fd43db49b..16b461aafe32bd91fa6d2b4573737993f69becf0 100644
--- a/test/metabase/pulse_test.clj
+++ b/test/metabase/pulse_test.clj
@@ -810,7 +810,7 @@
                                               :async?   true}}]
       (is (schema= {:card   (s/pred map?)
                     :result (s/pred map?)}
-                   (pulse/execute-card {} card))))))
+                   (pulse/execute-card {:creator_id (mt/user->id :rasta)} card))))))
 
 (deftest pulse-permissions-test
   (testing "Pulses should be sent with the Permissions of the user that created them."
diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj
index 73b27d1d7bc5bdb60bc18a5a7ccf07c0c67da230..9e601b3bcbe591a91f2325482288a1061ea1b9be 100644
--- a/test/metabase/query_processor_test/breakout_test.clj
+++ b/test/metabase/query_processor_test/breakout_test.clj
@@ -15,8 +15,7 @@
              [add-dimension-projections :as add-dim-projections]
              [add-source-metadata :as add-source-metadata]]
             [metabase.query-processor.test-util :as qp.test-util]
-            [metabase.test.data :as data]
-            [toucan.db :as db]))
+            [metabase.test.data :as data]))
 
 (deftest basic-test
   (mt/test-drivers (mt/normal-drivers)
@@ -72,8 +71,7 @@
 
 (deftest internal-remapping-test
   (mt/test-drivers (mt/normal-drivers)
-    (mt/with-temp-objects
-      (data/create-venue-category-remapping! "Foo")
+    (data/with-venue-category-remapping "Foo"
       (let [{:keys [rows cols]} (qp.test/rows-and-cols
                                   (mt/format-rows-by [int int str]
                                     (mt/run-mbql-query venues
@@ -93,12 +91,10 @@
 
 (deftest order-by-test
   (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys)
-    (mt/with-temp-objects
-      (fn []
-        [(db/insert! Dimension {:field_id                (data/id :venues :category_id)
+    (mt/with-temp Dimension [_ {:field_id                (data/id :venues :category_id)
                                 :name                    "Foo"
                                 :type                    :external
-                                :human_readable_field_id (data/id :categories :name)})])
+                                :human_readable_field_id (data/id :categories :name)}]
       (doseq [[sort-order expected] {:desc ["Wine Bar" "Thai" "Thai" "Thai" "Thai" "Steakhouse" "Steakhouse"
                                             "Steakhouse" "Steakhouse" "Southern"]
                                      :asc  ["American" "American" "American" "American" "American" "American" "American"
diff --git a/test/metabase/query_processor_test/remapping_test.clj b/test/metabase/query_processor_test/remapping_test.clj
index c1c7fc81f58a0490aa8486210bdb3249a1b7daba..29645f39660a9d442deb331a26da0c50d49ccf87 100644
--- a/test/metabase/query_processor_test/remapping_test.clj
+++ b/test/metabase/query_processor_test/remapping_test.clj
@@ -9,39 +9,36 @@
              [field :refer [Field]]]
             [metabase.query-processor.middleware.add-dimension-projections :as add-dimension-projections]
             [metabase.test.data :as data]
-            [metabase.test.data.datasets :as datasets]
             [toucan.db :as db]))
 
 (deftest basic-remapping-test
   (mt/test-drivers (mt/normal-drivers)
-    (mt/with-temp-objects
-      (data/create-venue-category-remapping! "Foo")
+    (data/with-venue-category-remapping "Foo"
       (is (= {:rows [["20th Century Cafe"               12 "Café"]
                      ["25°"                             11 "Burger"]
                      ["33 Taps"                          7 "Bar"]
                      ["800 Degrees Neapolitan Pizzeria" 58 "Pizza"]]
-              :cols [(qp.test/col :venues :name)
-                     (assoc (qp.test/col :venues :category_id) :remapped_to "Foo")
+              :cols [(mt/col :venues :name)
+                     (assoc (mt/col :venues :category_id) :remapped_to "Foo")
                      (#'add-dimension-projections/create-remapped-col "Foo" (mt/format-name "category_id") :type/Text)]}
              (qp.test/rows-and-cols
-               (qp.test/format-rows-by [str int str]
+               (mt/format-rows-by [str int str]
                  (mt/run-mbql-query venues
                    {:fields   [$name $category_id]
                     :order-by [[:asc $name]]
                     :limit    4})))))))
   (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys)
-    (mt/with-temp-objects
-      (data/create-venue-category-fk-remapping! "Name")
+    (data/with-venue-category-fk-remapping "Name"
       (is (= {:rows [["American" 2 8]
                      ["Artisan"  3 2]
                      ["Asian"    4 2]]
-              :cols [(-> (qp.test/col :categories :name)
+              :cols [(-> (mt/col :categories :name)
                          (assoc :remapped_from (mt/format-name "category_id"))
                          (assoc :field_ref [:fk-> [:field-id (mt/id :venues :category_id)]
                                             [:field-id (mt/id :categories :name)]])
                          (assoc :fk_field_id (mt/id :venues :category_id))
                          (assoc :source :breakout))
-                     (-> (qp.test/col :venues :category_id)
+                     (-> (mt/col :venues :category_id)
                          (assoc :remapped_to (mt/format-name "name"))
                          (assoc :source :breakout))
                      {:field_ref    [:aggregation 0]
@@ -49,7 +46,7 @@
                       :display_name "Count"
                       :name         "count"
                       :special_type :type/Number}]}
-             (-> (qp.test/format-rows-by [str int int]
+             (-> (mt/format-rows-by [str int int]
                    (mt/run-mbql-query venues
                      {:aggregation [[:count]]
                       :breakout    [$category_id]
@@ -60,21 +57,20 @@
 
 (deftest nested-remapping-test
   (mt/test-drivers (mt/normal-drivers-with-feature :nested-queries)
-    (mt/with-temp-objects
-      (data/create-venue-category-remapping! "Foo")
+    (data/with-venue-category-remapping "Foo"
       (is (= {:rows [["20th Century Cafe"               12 "Café"]
                      ["25°"                             11 "Burger"]
                      ["33 Taps"                          7 "Bar"]
                      ["800 Degrees Neapolitan Pizzeria" 58 "Pizza"]]
-              :cols [(-> (qp.test/col :venues :name)
+              :cols [(-> (mt/col :venues :name)
                          (assoc :field_ref [:field-literal (mt/format-name "name") :type/Text])
                          (dissoc :description :parent_id :visibility_type))
-                     (-> (qp.test/col :venues :category_id)
+                     (-> (mt/col :venues :category_id)
                          (assoc :remapped_to "Foo")
                          (assoc :field_ref [:field-literal (mt/format-name"category_id")])
                          (dissoc :description :parent_id :visibility_type))
                      (#'add-dimension-projections/create-remapped-col "Foo" (mt/format-name "category_id") :type/Text)]}
-             (-> (qp.test/format-rows-by [str int str]
+             (-> (mt/format-rows-by [str int str]
                    (mt/run-mbql-query venues
                      {:source-query {:source-table (mt/id :venues)
                                      :fields       [[:field-id (mt/id :venues :name)]
@@ -102,99 +98,96 @@
                  :when (columns-pred (:name col))]
              col)}))
 
-(datasets/expect-with-drivers (mt/normal-drivers-with-feature :foreign-keys)
-  {:rows [["20th Century Cafe"               2 "Café"]
-          ["25°"                             2 "Burger"]
-          ["33 Taps"                         2 "Bar"]
-          ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
-   :cols [(qp.test/col :venues :name)
-          (qp.test/col :venues :price)
-          (mt/$ids venues
-            (assoc (qp.test/col :categories :name)
-              :fk_field_id   %category_id
-              :display_name  "Foo"
-              :name          (mt/format-name "name_2")
-              :remapped_from (mt/format-name "category_id")
-              :field_ref     $category_id->categories.name))]}
-  (mt/with-temp-objects
-    (data/create-venue-category-fk-remapping! "Foo")
-    (select-columns (set (map mt/format-name ["name" "price" "name_2"]))
-      (qp.test/format-rows-by [int str int double double int str]
-        (mt/run-mbql-query venues
-          {:order-by [[:asc $name]]
-           :limit    4})))))
-
-;; Check that we can have remappings when we include a `:fields` clause that restricts the query fields returned
-(datasets/expect-with-drivers (mt/normal-drivers-with-feature :foreign-keys)
-  {:rows        [["20th Century Cafe"               2 "Café"]
-                 ["25°"                             2 "Burger"]
-                 ["33 Taps"                         2 "Bar"]
-                 ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
-   :cols        [(qp.test/col :venues :name)
-                 (qp.test/col :venues :price)
-                 (mt/$ids venues
-                   (assoc (qp.test/col :categories :name)
-                     :fk_field_id   %category_id
-                     :display_name  "Foo"
-                     :name          (mt/format-name "name_2")
-                     :remapped_from (mt/format-name "category_id")
-                     :field_ref     $category_id->categories.name))]}
-  (mt/with-temp-objects
-    (data/create-venue-category-fk-remapping! "Foo")
-    (select-columns (set (map mt/format-name ["name" "price" "name_2"]))
-      (qp.test/format-rows-by [str int str str]
-        (mt/run-mbql-query venues
-          {:fields   [$name $price $category_id]
-           :order-by [[:asc $name]]
-           :limit    4})))))
-
-;; Test that we can remap inside an MBQL query
-(datasets/expect-with-drivers (mt/normal-drivers-with-feature :foreign-keys :nested-queries)
-  ["Kinaree Thai Bistro" "Ruen Pair Thai Restaurant" "Yamashiro Hollywood" "Spitz Eagle Rock" "The Gumbo Pot"]
-  (mt/with-temp-objects
-    (fn []
-      [(db/insert! Dimension {:field_id                (mt/id :checkins :venue_id)
-                              :name                    "venue-remapping"
-                              :type                    :external
-                              :human_readable_field_id (mt/id :venues :name)})])
-    (->> (mt/run-mbql-query checkins
-           {:order-by [[:asc $date]]
-            :limit    5})
-         qp.test/rows
-         (map last))))
+(deftest foreign-keys-test
+  (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys)
+    (data/with-venue-category-fk-remapping "Foo"
+      (is (= {:rows [["20th Century Cafe"               2 "Café"]
+                     ["25°"                             2 "Burger"]
+                     ["33 Taps"                         2 "Bar"]
+                     ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
+              :cols [(mt/col :venues :name)
+                     (mt/col :venues :price)
+                     (mt/$ids venues
+                       (assoc (mt/col :categories :name)
+                              :fk_field_id   %category_id
+                              :display_name  "Foo"
+                              :name          (mt/format-name "name_2")
+                              :remapped_from (mt/format-name "category_id")
+                              :field_ref     $category_id->categories.name))]}
+             (select-columns (set (map mt/format-name ["name" "price" "name_2"]))
+               (mt/format-rows-by [int str int double double int str]
+                 (mt/run-mbql-query venues
+                   {:order-by [[:asc $name]]
+                    :limit    4}))))))))
 
-;; Test a remapping with conflicting names, in the case below there are two name fields, one from Venues and the other
-;; from Categories
-(datasets/expect-with-drivers (mt/normal-drivers-with-feature :foreign-keys :nested-queries)
-  ["20th Century Cafe" "25°" "33 Taps" "800 Degrees Neapolitan Pizzeria"]
-  (mt/with-temp-objects
-    (data/create-venue-category-fk-remapping! "Foo")
-    (->> (qp.test/rows
-           (mt/run-mbql-query venues
-             {:order-by [[:asc $name]], :limit 4}))
-         (map second))))
+(deftest remappings-with-field-clause-test
+  (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys)
+    (testing (str "Check that we can have remappings when we include a `:fields` clause that restricts the query "
+                  "fields returned")
+      (data/with-venue-category-fk-remapping "Foo"
+        (is (= {:rows [["20th Century Cafe"               2 "Café"]
+                       ["25°"                             2 "Burger"]
+                       ["33 Taps"                         2 "Bar"]
+                       ["800 Degrees Neapolitan Pizzeria" 2 "Pizza"]]
+                :cols [(mt/col :venues :name)
+                       (mt/col :venues :price)
+                       (mt/$ids venues
+                         (assoc (mt/col :categories :name)
+                                :fk_field_id   %category_id
+                                :display_name  "Foo"
+                                :name          (mt/format-name "name_2")
+                                :remapped_from (mt/format-name "category_id")
+                                :field_ref     $category_id->categories.name))]}
+               (select-columns (set (map mt/format-name ["name" "price" "name_2"]))
+                 (mt/format-rows-by [str int str str]
+                   (mt/run-mbql-query venues
+                     {:fields   [$name $price $category_id]
+                      :order-by [[:asc $name]]
+                      :limit    4})))))))))
 
-;; Test out a self referencing column. This has a users table like the one that is in `test-data`, but also includes a
-;; `created_by` column which references the PK column in that same table. This tests that remapping table aliases are
-;; handled correctly
-;;
-;; Having a self-referencing FK is currently broken with the Redshift and Oracle backends. The issue related to fix
-;; this is https://github.com/metabase/metabase/issues/8510
-(datasets/expect-with-drivers (disj (mt/normal-drivers-with-feature :foreign-keys) :redshift :oracle :vertica)
-  ["Dwight Gresham" "Shad Ferdynand" "Kfir Caj" "Plato Yeshua"]
-  (mt/dataset test-data-self-referencing-user
-    (mt/with-temp-objects
-      (fn []
-        [(db/insert! Dimension {:field_id                (mt/id :users :created_by)
-                                :name                    "created-by-mapping"
-                                :type                    :external
-                                :human_readable_field_id (mt/id :users :name)})])
+(deftest remap-inside-mbql-query-test
+  (testing "Test that we can remap inside an MBQL query"
+    (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys :nested-queries)
+      (mt/with-temp Dimension [_ {:field_id                (mt/id :checkins :venue_id)
+                                  :name                    "venue-remapping"
+                                  :type                    :external
+                                  :human_readable_field_id (mt/id :venues :name)}]
+        (is (= ["Kinaree Thai Bistro" "Ruen Pair Thai Restaurant" "Yamashiro Hollywood" "Spitz Eagle Rock" "The Gumbo Pot"]
+               (->> (mt/run-mbql-query checkins
+                      {:order-by [[:asc $date]]
+                       :limit    5})
+                    mt/rows
+                    (map last))))))))
 
-      (db/update! 'Field (mt/id :users :created_by)
-        {:fk_target_field_id (mt/id :users :id)})
+(deftest remapping-with-conflicting-names-test
+  (mt/test-drivers (mt/normal-drivers-with-feature :foreign-keys :nested-queries)
+    (testing (str "Test a remapping with conflicting names, in the case below there are two name fields, one from "
+                  "Venues and the other from Categories")
+      (data/with-venue-category-fk-remapping "Foo"
+        (is (= ["20th Century Cafe" "25°" "33 Taps" "800 Degrees Neapolitan Pizzeria"]
+               (->> (mt/rows
+                      (mt/run-mbql-query venues
+                        {:order-by [[:asc $name]], :limit 4}))
+                    (map second))))))))
 
-      (->> (mt/run-mbql-query users
-             {:order-by [[:asc $name]]
-              :limit    4})
-           qp.test/rows
-           (map last)))))
+(deftest self-referencing-test
+  ;; Test out a self referencing column. This has a users table like the one that is in `test-data`, but also includes a
+  ;; `created_by` column which references the PK column in that same table. This tests that remapping table aliases are
+  ;; handled correctly
+  ;;
+  ;; Having a self-referencing FK is currently broken with the Redshift and Oracle backends. The issue related to fix
+  ;; this is https://github.com/metabase/metabase/issues/8510
+  (mt/test-drivers (disj (mt/normal-drivers-with-feature :foreign-keys) :redshift :oracle :vertica)
+    (mt/dataset test-data-self-referencing-user
+      (mt/with-temp Dimension [_ {:field_id                (mt/id :users :created_by)
+                                  :name                    "created-by-mapping"
+                                  :type                    :external
+                                  :human_readable_field_id (mt/id :users :name)}]
+        (db/update! 'Field (mt/id :users :created_by)
+          {:fk_target_field_id (mt/id :users :id)})
+        (is (= ["Dwight Gresham" "Shad Ferdynand" "Kfir Caj" "Plato Yeshua"]
+               (->> (mt/run-mbql-query users
+                      {:order-by [[:asc $name]]
+                       :limit    4})
+                    mt/rows
+                    (map last))))))))
diff --git a/test/metabase/sync/analyze/fingerprint_test.clj b/test/metabase/sync/analyze/fingerprint_test.clj
index dea1e3ae6a4c6b8d86b63c3e77f2e6d8b9f0ecb2..bf03c0e95e9d61cd613b7de764546355e46fd282 100644
--- a/test/metabase/sync/analyze/fingerprint_test.clj
+++ b/test/metabase/sync/analyze/fingerprint_test.clj
@@ -229,6 +229,19 @@
         (is (= (fingerprint/empty-stats-map 0)
                (fingerprint/fingerprint-fields-for-db! fake-db [(Table (data/id :venues))] (fn [_ _]))))))))
 
+(deftest fingerprint-test
+  (mt/test-drivers (mt/normal-drivers)
+    (testing "Fingerprints should actually get saved with the correct values"
+      (testing "Text fingerprints"
+        (is (schema= {:global {:distinct-count (s/eq 100)
+                               :nil%           (s/eq 0.0)}
+                      :type   {:type/Text {:percent-json   (s/eq 0.0)
+                                           :percent-url    (s/eq 0.0)
+                                           :percent-email  (s/eq 0.0)
+                                           :average-length (s/pred #(< 15 % 16) "between 15 and 16")
+                                           :percent-state  (s/eq 0.0)}}}
+                     (db/select-one-field :fingerprint Field :id (mt/id :venues :name))))))))
+
 (deftest fingerprinting-test
   (testing "fingerprinting truncates text fields (see #13288)"
     (doseq [size [4 8 10]]
diff --git a/test/metabase/sync/field_values_test.clj b/test/metabase/sync/field_values_test.clj
index 3605b3ed498a0f74dabbae39b95675ea6447de7a..b8a2d6b1fc640fcdbac052f6aa545179cd55bb40 100644
--- a/test/metabase/sync/field_values_test.clj
+++ b/test/metabase/sync/field_values_test.clj
@@ -1,82 +1,104 @@
 (ns metabase.sync.field-values-test
   "Tests around the way Metabase syncs FieldValues, and sets the values of `field.has_field_values`."
   (:require [clojure.test :refer :all]
+            [metabase
+             [sync :as sync]
+             [test :as mt]]
             [metabase.models
              [field :refer [Field]]
              [field-values :as field-values :refer [FieldValues]]
              [table :refer [Table]]]
-            [metabase.sync :as sync]
             [metabase.sync.util-test :as sut]
             [metabase.test.data :as data]
             [metabase.test.data.one-off-dbs :as one-off-dbs]
             [toucan.db :as db]))
 
-;; Test that when we delete FieldValues syncing the Table again will cause them to be re-created
 (defn- venues-price-field-values []
-  (db/select-one-field :values FieldValues, :field_id (data/id :venues :price)))
+  (db/select-one-field :values FieldValues, :field_id (mt/id :venues :price)))
 
 (defn- sync-database!' [step database]
   (let [{:keys [step-info task-history]} (sut/sync-database! step database)]
     [(sut/only-step-keys step-info)
      (:task_details task-history)]))
 
-(deftest field-updating-test
-  (testing "syncing updates nil field values"
-    (are [expected op] (= expected op)
-      [1 2 3 4] (venues-price-field-values)
-      nil       (do (db/delete! FieldValues :field_id (data/id :venues :price))
-                    (venues-price-field-values))
+(deftest sync-recreate-field-values-test
+  (testing "Test that when we delete FieldValues syncing the Table again will cause them to be re-created"
+    (testing "Check that we have expected field values to start with"
+      (is (= [1 2 3 4]
+             (venues-price-field-values))))
+    (testing "Delete the Field values, make sure they're gone"
+      (db/delete! FieldValues :field_id (mt/id :venues :price))
+      (is (= nil
+             (venues-price-field-values))))
+    (testing "After the delete, a field values should be created, the rest updated"
+      (is (= (repeat 2 {:errors 0, :created 1, :updated 0, :deleted 0})
+             (sync-database!' "update-field-values" (data/db)))))
+    (testing "Now re-sync the table and make sure they're back"
+      (sync/sync-table! (Table (mt/id :venues)))
+      (is (= [1 2 3 4]
+             (venues-price-field-values))))))
 
-      (repeat 2 {:errors 0, :created 1, :updated 0, :deleted 0})
-      (sync-database!' "update-field-values" (data/db))
+(deftest sync-should-update-test
+  (testing "Test that syncing will cause FieldValues to be updated"
+    (testing "Check that we have expected field values to start with"
+      (is (= [1 2 3 4]
+             (venues-price-field-values))))
+    (testing "Update the FieldValues, remove one of the values that should be there"
+      (db/update! FieldValues (db/select-one-id FieldValues :field_id (mt/id :venues :price)) :values [1 2 3])
+      (is (= [1 2 3]
+             (venues-price-field-values))))
+    (testing "Now re-sync the table and validate the field values updated"
+      (is (= (repeat 2 {:errors 0, :created 0, :updated 1, :deleted 0})
+             (sync-database!' "update-field-values" (data/db)))))
+    (testing "Make sure the value is back"
+      (is (= [1 2 3 4]
+             (venues-price-field-values))))))
 
-      [1 2 3 4] (do (sync/sync-table! (Table (data/id :venues)))
-                    (venues-price-field-values))))
-  (testing "syncing will cause FieldValues to be updated"
-    (are [expected op] (= expected op)
-      [1 2 3 4] (venues-price-field-values)
-      [1 2 3  ] (do (db/update! FieldValues (db/select-one-id FieldValues :field_id (data/id :venues :price))
-                      :values [1 2 3])
-                    (venues-price-field-values))
+(deftest auto-list-test
+  ;; A Field with 50 values should get marked as `auto-list` on initial sync, because it should be 'list', but was
+  ;; marked automatically, as opposed to explicitly (`list`)
+  (one-off-dbs/with-blueberries-db
+    ;; insert 50 rows & sync
+    (one-off-dbs/insert-rows-and-sync! (range 50))
+    (testing "has_field_values should be auto-list"
+      (is (= :auto-list
+             (db/select-one-field :has_field_values Field :id (mt/id :blueberries_consumed :num)))))
 
-      (repeat 2 {:errors 0, :created 0, :updated 1, :deleted 0})
-      (sync-database!' "update-field-values" (data/db))
+    (testing "... and it should also have some FieldValues"
+      (is (= {:values                (range 50)
+              :human_readable_values []}
+             (into {} (db/select-one [FieldValues :values :human_readable_values]
+                        :field_id (mt/id :blueberries_consumed :num))))))
 
-      [1 2 3 4] (venues-price-field-values))))
+    (testing (str "if the number grows past the threshold & we sync again it should get unmarked as auto-list and set "
+                  "back to `nil` (#3215)\n")
+      ;; now insert enough bloobs to put us over the limit and re-sync.
+      (one-off-dbs/insert-rows-and-sync! (range 50 (+ 100 field-values/auto-list-cardinality-threshold)))
+      (testing "has_field_values should have been set to nil."
+        (is (= nil
+               (db/select-one-field :has_field_values Field :id (mt/id :blueberries_consumed :num)))))
 
-(deftest auto-list-tests
-  (let [below-threshold        (range (dec field-values/auto-list-cardinality-threshold))
-        above-threshold        (range (inc field-values/auto-list-cardinality-threshold))
-        really-above-threshold (range (+ 100 field-values/auto-list-cardinality-threshold))
-        field-info             #(db/select-one-field :has_field_values Field
-                                  :id (data/id :blueberries_consumed :num))
-        cached-values          #(db/select-one [FieldValues :values :human_readable_values]
-                                  :field_id (data/id :blueberries_consumed :num))]
-    (testing "A field with few enough values"
-      (one-off-dbs/with-blueberries-db
-        (testing "should get marked as `auto-list` on initial sync"
-          (one-off-dbs/insert-rows-and-sync! below-threshold)
-          (is (= :auto-list (field-info))))
-        (testing "its values should be stored in the metabase db"
-          (is (= (field-values/map->FieldValuesInstance
-                   {:values (vec below-threshold), :human_readable_values {}})
-                 (cached-values))))
-        (testing "If it grows larger than the threshold"
-          (one-off-dbs/insert-rows-and-sync! below-threshold)
-          (testing "It loses the `auto-list`"
-            (one-off-dbs/insert-rows-and-sync! above-threshold)
-            (is (nil? (field-info))))
-          (testing "It loses the stored values in the metabase db"
-            (testing "After updating loses auto-list"
-              (one-off-dbs/insert-rows-and-sync! above-threshold)
-              (is (nil? (field-info)))
-              (is (nil? (cached-values))))))))
-    (testing "If marked as `list` adding extra values should not remove anything"
-      (one-off-dbs/with-blueberries-db
-        (one-off-dbs/insert-rows-and-sync! below-threshold)
-        (is (= :auto-list (field-info)))
-        ;; human marks as list
-        (db/update! Field (data/id :blueberries_consumed :num) :has_field_values "list")
-        (one-off-dbs/insert-rows-and-sync! really-above-threshold)
-        (is (= :list (field-info)))
-        (is (= (count really-above-threshold) (-> (cached-values) :values count)))))))
+      (testing "its FieldValues should also get deleted."
+        (is (= nil
+               (db/select-one [FieldValues :values :human_readable_values]
+                 :field_id (mt/id :blueberries_consumed :num))))))))
+
+(deftest list-test
+  (testing (str "If we had explicitly marked the Field as `list` (instead of `auto-list`), adding extra values "
+                "shouldn't change anything!")
+    (one-off-dbs/with-blueberries-db
+      ;; insert 50 bloobs & sync
+      (one-off-dbs/insert-rows-and-sync! (range 50))
+      ;; change has_field_values to list
+      (db/update! Field (mt/id :blueberries_consumed :num) :has_field_values "list")
+      ;; insert more bloobs & re-sync
+      (one-off-dbs/insert-rows-and-sync! (range 50 (+ 100 field-values/auto-list-cardinality-threshold)))
+      (testing "has_field_values shouldn't change"
+        (is (= :list
+               (db/select-one-field :has_field_values Field :id (mt/id :blueberries_consumed :num)))))
+      (testing (str "it should still have FieldValues, and have new ones for the new Values. It should have 200 values "
+                    "even though this is past the normal limit of 100 values!")
+        (is (= {:values                (range 200)
+                :human_readable_values []}
+               (into {} (db/select-one [FieldValues :values :human_readable_values]
+                          :field_id (mt/id :blueberries_consumed :num)))))))))
diff --git a/test/metabase/test.clj b/test/metabase/test.clj
index 7d6fee04b699331ceab47f7d9abfa520f5c8c3e7..0e04cb3bfafe48095c52984b7d95743a2b7ba9b3 100644
--- a/test/metabase/test.clj
+++ b/test/metabase/test.clj
@@ -14,8 +14,10 @@
              [email-test :as et]
              [http-client :as http]
              [query-processor :as qp]
-             [query-processor-test :as qp.test]]
+             [query-processor-test :as qp.test]
+             [util :as u]]
             [metabase.driver.sql-jdbc.test-util :as sql-jdbc.tu]
+            [metabase.plugins.classloader :as classloader]
             [metabase.query-processor
              [context :as qp.context]
              [reducible :as qp.reducible]
@@ -48,6 +50,7 @@
   http/keep-me
   i18n.tu/keep-me
   initialize/keep-me
+  mt.tu/keep-me
   qp/keep-me
   qp.test-util/keep-me
   qp.test/keep-me
@@ -74,8 +77,7 @@
   query
   run-mbql-query
   with-db
-  with-temp-copy-of-db
-  with-temp-objects]
+  with-temp-copy-of-db]
 
  [datasets
   test-driver
@@ -208,6 +210,11 @@
   set-test-drivers!
   with-test-drivers])
 
+;; ee-only stuff
+(u/ignore-exceptions
+  (classloader/require 'metabase-enterprise.sandbox.test-util)
+  (eval '(potemkin/import-vars [metabase-enterprise.sandbox.test-util with-gtaps])))
+
 ;; TODO -- move this stuff into some other namespace and refer to it here
 
 (defn do-with-clock [clock thunk]
diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj
index 8da68ab620a7a19608ff90a102adf53cfcf00766..b438790247055a3c028d811057b590a73a216549 100644
--- a/test/metabase/test/data.clj
+++ b/test/metabase/test/data.clj
@@ -33,8 +33,7 @@
       ;; -> {:source-table (data/id :venues), :fields [(data/id :venues :name)]}
 
      (There are several variations of this macro; see documentation below for more details.)"
-  (:require [cheshire.core :as json]
-            [clojure.test :as t]
+  (:require [clojure.test :as t]
             [colorize.core :as colorize]
             [medley.core :as m]
             [metabase
@@ -49,7 +48,7 @@
              [impl :as impl]
              [interface :as tx]
              [mbql-query-impl :as mbql-query-impl]]
-            [toucan.db :as db]))
+            [toucan.util.test :as tt]))
 
 ;;; ------------------------------------------ Dataset-Independent Data Fns ------------------------------------------
 
@@ -244,11 +243,6 @@
   [& body]
   `(impl/do-with-temp-copy-of-db (fn [] ~@body)))
 
-(defmacro with-temp-objects
-  "Calls `data-load-fn` to create a sequence of Toucan objects, then runs `body`; finally, deletes the objects."
-  [data-load-fn & body]
-  `(impl/do-with-temp-objects ~data-load-fn (fn [] ~@body)))
-
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                          Rarely-Used Helper Functions                                          |
@@ -289,25 +283,24 @@
 (def ^:private category-names
   (delay (vec (dataset-field-values "categories" "name"))))
 
-;; TODO - you should always call these functions with the `with-data` macro. We should enforce this
-(defn create-venue-category-remapping!
-  "Returns a thunk that adds an internal remapping for category_id in the venues table aliased as `remapping-name`.
-  Can be used in a `with-data` invocation."
-  [remapping-name]
-  (fn []
-    [(db/insert! Dimension {:field_id (id :venues :category_id)
-                            :name     remapping-name
-                            :type     :internal})
-     (db/insert! FieldValues {:field_id              (id :venues :category_id)
-                              :values                (json/generate-string (range 1 (inc (count @category-names))))
-                              :human_readable_values (json/generate-string @category-names)})]))
-
-(defn create-venue-category-fk-remapping!
-  "Returns a thunk that adds a FK remapping for category_id in the venues table aliased as `remapping-name`. Can be
-  used in a `with-data` invocation."
-  [remapping-name]
-  (fn []
-    [(db/insert! Dimension {:field_id                (id :venues :category_id)
-                            :name                    remapping-name
-                            :type                    :external
-                            :human_readable_field_id (id :categories :name)})]))
+(defn do-with-venue-category-remapping [remapping-name thunk]
+  (tt/with-temp* [Dimension   [_ {:field_id (id :venues :category_id)
+                                  :name     remapping-name
+                                  :type     :internal}]
+                  FieldValues [_ {:field_id              (id :venues :category_id)
+                                  :values                (range 1 (inc (count @category-names)))
+                                  :human_readable_values @category-names}]]
+    (thunk)))
+
+(defmacro with-venue-category-remapping [remapping-name & body]
+  `(do-with-venue-category-remapping ~remapping-name (fn [] ~@body)))
+
+(defn do-with-venue-category-fk-remapping [remapping-name thunk]
+  (tt/with-temp Dimension [_ {:field_id                (id :venues :category_id)
+                              :name                    remapping-name
+                              :type                    :external
+                              :human_readable_field_id (id :categories :name)}]
+    (thunk)))
+
+(defmacro with-venue-category-fk-remapping [remapping-name & body]
+  `(do-with-venue-category-fk-remapping ~remapping-name (fn [] ~@body)))
diff --git a/test/metabase/test/data/impl.clj b/test/metabase/test/data/impl.clj
index 88ab622cb7a7d37a6ed50454f0cb99097392af83..320e4d2f99f26ae253a150bd645ad6a7ea2cc071 100644
--- a/test/metabase/test/data/impl.clj
+++ b/test/metabase/test/data/impl.clj
@@ -293,24 +293,3 @@
                                  db))))]
     (binding [*get-db* #(get-db-for-driver (tx/driver))]
       (f))))
-
-
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                               with-temp-objects                                                |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-(defn- delete-model-instance!
-  "Allows deleting a row by the model instance toucan returns when it's inserted"
-  [{:keys [id] :as instance}]
-  (db/delete! (-> instance name symbol) :id id))
-
-(defn do-with-temp-objects
-  "Internal impl of `data/with-data`. Takes a thunk `data-load-fn` that returns a seq of Toucan model instances that
-  will be deleted after `body-fn` finishes"
-  [data-load-fn body-fn]
-  (let [result-instances (data-load-fn)]
-    (try
-      (body-fn)
-      (finally
-        (doseq [instance result-instances]
-          (delete-model-instance! instance))))))
diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj
index c52af6a748bb1a5ba01bce794095697ad324bdf4..aba9e2a1c282bcd2f6cd412f544d65e1490d875b 100644
--- a/test/metabase/test/util.clj
+++ b/test/metabase/test/util.clj
@@ -550,6 +550,7 @@
 
 (defn do-with-model-cleanup [models f]
   {:pre [(sequential? models) (every? t.models/model? models)]}
+  (initialize/initialize-if-needed! :db)
   (let [model->old-max-id (into {} (for [model models]
                                      [model (:max-id (db/select-one [model [:%max.id :max-id]]))]))]
     (try
@@ -557,7 +558,9 @@
         (f))
       (finally
         (doseq [model models
-                :let  [old-max-id (get model->old-max-id model)]]
+                ;; might not have an old max ID if this is the first time the macro is used in this test run.
+                :let  [old-max-id (or (get model->old-max-id model)
+                                      0)]]
           (db/delete! model :id [:> old-max-id]))))))
 
 (defmacro with-model-cleanup
@@ -663,25 +666,35 @@
   [collection-or-id & body]
   `(do-with-discarded-collections-perms-changes ~collection-or-id (fn [] ~@body)))
 
-(defn do-with-non-admin-groups-no-root-collection-perms [f]
-  (initialize/initialize-if-needed! :db)
+(defn do-with-non-admin-groups-no-collection-perms [collection f]
   (try
-    (doseq [group-id (db/select-ids PermissionsGroup :id [:not= (u/get-id (group/admin))])]
-      (perms/revoke-collection-permissions! group-id collection/root-collection))
-    (f)
+    (do-with-discarded-collections-perms-changes
+     collection
+     (fn []
+       (db/delete! Permissions
+         :object [:in #{(perms/collection-read-path collection) (perms/collection-readwrite-path collection)}]
+         :group_id [:not= (u/get-id (group/admin))])
+       (f)))
+    ;; if this is the default namespace Root Collection, then double-check to make sure all non-admin groups get
+    ;; perms for it at the end. This is here mostly for legacy reasons; we can remove this but it will require
+    ;; rewriting a few tests.
     (finally
-      (doseq [group-id (db/select-ids PermissionsGroup :id [:not= (u/get-id (group/admin))])]
-        (when-not (db/exists? Permissions
-                    :group_id group-id
-                    :object   (perms/collection-readwrite-path collection/root-collection))
-          (perms/grant-collection-readwrite-permissions! group-id collection/root-collection))))))
+      (when (and (:metabase.models.collection.root/is-root? collection)
+                 (not (:namespace collection)))
+        (doseq [group-id (db/select-ids PermissionsGroup :id [:not= (u/get-id (group/admin))])]
+          (when-not (db/exists? Permissions :group_id group-id, :object "/collection/root/")
+            (perms/grant-collection-readwrite-permissions! group-id collection/root-collection)))))))
 
 (defmacro with-non-admin-groups-no-root-collection-perms
   "Temporarily remove Root Collection perms for all Groups besides the Admin group (which cannot have them removed). By
   default, all Groups have full readwrite perms for the Root Collection; use this macro to test situations where an
-  admin has removed them."
+  admin has removed them.
+
+  Only affects the Root Collection for the default namespace. Use
+  `with-non-admin-groups-no-root-collection-for-namespace-perms` to do the same thing for the Root Collection of other
+  namespaces."
   [& body]
-  `(do-with-non-admin-groups-no-root-collection-perms (fn [] ~@body)))
+  `(do-with-non-admin-groups-no-collection-perms collection/root-collection (fn [] ~@body)))
 
 (defmacro with-non-admin-groups-no-root-collection-for-namespace-perms
   "Like `with-non-admin-groups-no-root-collection-perms`, but for the Root Collection of a non-default namespace."
diff --git a/test/metabase/transforms/core_test.clj b/test/metabase/transforms/core_test.clj
index 5eed5edfd1b39376cffb95ccc079eb4e12d835a6..3f212fff68d3ded2395d5413ff59754857c760d7 100644
--- a/test/metabase/transforms/core_test.clj
+++ b/test/metabase/transforms/core_test.clj
@@ -1,6 +1,5 @@
 (ns metabase.transforms.core-test
   (:require [clojure.test :refer :all]
-            [expectations :refer [expect]]
             [medley.core :as m]
             [metabase
              [query-processor :as qp]
@@ -11,7 +10,6 @@
              [specs :as de.specs]]
             [metabase.models
              [card :as card :refer [Card]]
-             [collection :refer [Collection]]
              [table :as table :refer [Table]]]
             [metabase.test
              [domain-entities :refer :all]
@@ -28,30 +26,24 @@
        {"Venues" {:dimensions (m/map-vals de/mbql-reference (get-in table [:domain_entity :dimensions]))
                   :entity     table}}))))
 
-;; Can we accure bindings?
-(let [new-bindings {"D2" [:sum [:field-id 4]]
-                    "D3" [:field-id 5]}]
-  (expect
-    (update-in @test-bindings ["Venues" :dimensions] merge new-bindings)
-    (#'t/add-bindings @test-bindings "Venues" new-bindings)))
-
-;; Gracefully handle nil
-(expect
-    @test-bindings
-    (#'t/add-bindings @test-bindings "Venues" nil))
-
-
-(expect
-  "PRICE"
-  (#'t/mbql-reference->col-name [:field-id (mt/id :venues :price)]))
-
-(expect
-  "PRICE"
-  (#'t/mbql-reference->col-name [:field-literal "PRICE" :type/Integer]))
-
-(expect
-  "PRICE"
-  (#'t/mbql-reference->col-name [{:foo [:field-id (mt/id :venues :price)]}]))
+(deftest add-bindings-test
+  (testing "Can we accure bindings?"
+    (let [new-bindings {"D2" [:sum [:field-id 4]]
+                        "D3" [:field-id 5]}]
+      (is (= (update-in @test-bindings ["Venues" :dimensions] merge new-bindings)
+             (#'t/add-bindings @test-bindings "Venues" new-bindings)))))
+
+  (testing "Gracefully handle nil"
+    (is (= @test-bindings
+           (#'t/add-bindings @test-bindings "Venues" nil)))))
+
+(deftest mbql-reference->col-name-test
+  (is (= "PRICE"
+         (#'t/mbql-reference->col-name [:field-id (mt/id :venues :price)])))
+  (is (= "PRICE"
+         (#'t/mbql-reference->col-name [:field-literal "PRICE" :type/Integer])))
+  (is (= "PRICE"
+         (#'t/mbql-reference->col-name [{:foo [:field-id (mt/id :venues :price)]}]))))
 
 (deftest ->source-table-reference-test
   (testing "Can we turn a given entity into a format suitable for a query's `:source_table`?"
@@ -64,88 +56,76 @@
         (is (= (str "card__" card-id)
                (#'t/->source-table-reference (Card card-id))))))))
 
+(deftest tableset-test
+  (testing "Can we get a tableset for a given schema?"
+    (is (= (db/select-ids Table :db_id (mt/id))
+           (set (map u/get-id (#'t/tableset (mt/id) "PUBLIC")))))))
 
-;; Can we get a tableset for a given schema?
-(expect
-  (db/select-ids Table :db_id (mt/id))
-  (set (map u/get-id (#'t/tableset (mt/id) "PUBLIC"))))
-
-
-;; Can we filter a tableset by domain entity?
-(expect
-  [(mt/id :venues)]
-  (with-test-domain-entity-specs
-    (map u/get-id (#'t/find-tables-with-domain-entity (#'t/tableset (mt/id) "PUBLIC")
-                                                      (@de.specs/domain-entity-specs "Venues")))))
-
-;; Greacefully handle no-match
-(expect
-  nil
+(deftest find-tables-with-domain-entity-test
   (with-test-domain-entity-specs
-    (not-empty
-     (#'t/find-tables-with-domain-entity [] (@de.specs/domain-entity-specs "Venues")))))
-
+    (testing "Can we filter a tableset by domain entity?"
+      (is (= [(mt/id :venues)]
+             (map u/get-id (#'t/find-tables-with-domain-entity (#'t/tableset (mt/id) "PUBLIC")
+                                                               (@de.specs/domain-entity-specs "Venues"))))))
+    (testing "Gracefully handle no-match"
+      (with-test-domain-entity-specs
+        (is (= nil
+               (not-empty
+                (#'t/find-tables-with-domain-entity [] (@de.specs/domain-entity-specs "Venues")))))))))
 
-;; Can we extract results from the final bindings?
-(expect
-  [(mt/id :venues)]
-  (with-test-transform-specs
-    (map u/get-id (#'t/resulting-entities {"VenuesEnhanced" {:entity     (Table (mt/id :venues))
-                                                             :dimensions {"D1" [:field-id 1]}}}
-                                          (first @t.specs/transform-specs)))))
+(deftest resulting-entities-test
+  (testing "Can we extract results from the final bindings?"
+    (with-test-transform-specs
+      (is (= [(mt/id :venues)]
+             (map u/get-id (#'t/resulting-entities {"VenuesEnhanced" {:entity     (Table (mt/id :venues))
+                                                                      :dimensions {"D1" [:field-id 1]}}}
+                                                   (first @t.specs/transform-specs))))))))
 
+(deftest tables-matching-requirements-test
+  (testing "Can we find a table set matching requirements of a given spec?"
+    (with-test-transform-specs
+      (with-test-domain-entity-specs
+        (is (= [(mt/id :venues)]
+               (map u/get-id (#'t/tables-matching-requirements (#'t/tableset (mt/id) "PUBLIC")
+                                                               (first @t.specs/transform-specs)))))))))
 
-;; Can we find a table set matching requirements of a given spec?
-(expect
-  [(mt/id :venues)]
-  (with-test-transform-specs
+(deftest tableset->bindings-test
+  (testing "Can we turn a tableset into corresponding bindings?"
     (with-test-domain-entity-specs
-      (map u/get-id (#'t/tables-matching-requirements (#'t/tableset (mt/id) "PUBLIC")
-                                                      (first @t.specs/transform-specs))))))
-
-
-;; Can we turn a tableset into corresponding bindings?
-(expect
-  @test-bindings
-  (with-test-domain-entity-specs
-    (#'t/tableset->bindings (filter (comp #{(mt/id :venues)} u/get-id) (#'t/tableset (mt/id) "PUBLIC")))))
-
+      (is (= @test-bindings
+             (#'t/tableset->bindings (filter (comp #{(mt/id :venues)} u/get-id) (#'t/tableset (mt/id) "PUBLIC"))))))))
 
-;; Is the validation of results working?
-(expect
+(deftest validation-test
   (with-test-domain-entity-specs
     (with-test-transform-specs
-      (#'t/validate-results {"VenuesEnhanced" {:entity     (card/map->CardInstance
-                                                             {:result_metadata [{:name "AvgPrice"}
-                                                                                {:name "MaxPrice"}
-                                                                                {:name "MinPrice"}]})
-                                               :dimensions {"D1" [:field-id 1]}}}
-                            (first @t.specs/transform-specs)))))
-
-;; ... and do we throw if we didn't get what we expected?
-(expect
-  java.lang.AssertionError
-  (with-test-domain-entity-specs
-    (with-test-transform-specs
-      (#'t/validate-results {"VenuesEnhanced" {:entity     (Table (mt/id :venues))
-                                               :dimensions {"D1" [:field-id 1]}}}
-                            (first @t.specs/transform-specs)))))
-
+      (testing "Is the validation of results working?"
+        (is (#'t/validate-results {"VenuesEnhanced" {:entity     (card/map->CardInstance
+                                                                  {:result_metadata [{:name "AvgPrice"}
+                                                                                     {:name "MaxPrice"}
+                                                                                     {:name "MinPrice"}]})
+                                                     :dimensions {"D1" [:field-id 1]}}}
+                                  (first @t.specs/transform-specs))))
+
+      (testing "... and do we throw if we didn't get what we expected?"
+        (is (thrown?
+             java.lang.AssertionError
+             (#'t/validate-results {"VenuesEnhanced" {:entity     (Table (mt/id :venues))
+                                                      :dimensions {"D1" [:field-id 1]}}}
+                                   (first @t.specs/transform-specs))))))))
 
 (deftest transform-test
   (testing "Run the transform and make sure it produces the correct result"
     (mt/with-test-user :rasta
       (with-test-domain-entity-specs
-        (mt/with-model-cleanup [Card Collection]
-          (is (= [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5 4 3 2 1]
-                  [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0 11 2 1 1]
-                  [3 "The Apple Pan" 11 34.0406 -118.428 2 2.0 11 2 1 1]]
-                 (-> (t/apply-transform! (mt/id) "PUBLIC" test-transform-spec)
-                     first
-                     :dataset_query
-                     qp/process-query
-                     :data
-                     :rows))))))))
+        (is (= [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5 4 3 2 1]
+                [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 2.0 11 2 1 1]
+                [3 "The Apple Pan" 11 34.0406 -118.428 2 2.0 11 2 1 1]]
+               (-> (t/apply-transform! (mt/id) "PUBLIC" test-transform-spec)
+                   first
+                   :dataset_query
+                   qp/process-query
+                   :data
+                   :rows)))))))
 
 (deftest correct-transforms-for-table-test
   (testing "Can we find the right transform(s) for a given table"
diff --git a/test/metabase/util_test.clj b/test/metabase/util_test.clj
index cca9d6335232e5602d209d89cf9103bd294d40de..9268f1d6ebb39f4437636069551ffa1c2058e274 100644
--- a/test/metabase/util_test.clj
+++ b/test/metabase/util_test.clj
@@ -167,13 +167,21 @@
     {}                                         [:c]              {}))
 
 (deftest base64-string?-test
-  (are+ [s expected] (= expected
+  (are+ [s expected]    (= expected
                         (u/base64-string? s))
-    "ABc"           true
-    "ABc/+asdasd==" true
-    100             false
-    "<<>>"          false
-    "{\"a\": 10}"   false))
+    "ABc="         true
+    "ABc/+asdasd=" true
+    100            false
+    "<<>>"         false
+    "{\"a\": 10}"  false
+    ;; must be at least 2 characters...
+    "/"            false
+    ;; and end with padding if needed
+    "QQ"           false
+    "QQ="          false
+    "QQ=="         true
+    ;; padding has to go at the end
+    "==QQ"         false))
 
 (deftest select-keys-test
   (testing "select-non-nil-keys"
diff --git a/test_resources/ldap.ldif b/test_resources/ldap.ldif
index f2a2b8fcc21a10b4e9202f3273bd0a1e612332b0..1e1f315e462a807b9362e4c3cf38936c46d88505 100644
--- a/test_resources/ldap.ldif
+++ b/test_resources/ldap.ldif
@@ -74,6 +74,7 @@ sn: Pigeon
 uid: lucky
 mail: lucky@metabase.com
 userPassword: notalmonds
+title: King Pigeon
 
 dn: ou=Groups,dc=metabase,dc=com
 objectClass: top
diff --git a/test_resources/log4j2-test.xml b/test_resources/log4j2-test.xml
index 0826221114751a7120c6e2041a482408005a281e..0c51f4075d020f881a383d8c8320179c00575319 100644
--- a/test_resources/log4j2-test.xml
+++ b/test_resources/log4j2-test.xml
@@ -7,6 +7,7 @@
   </Appenders>
   <Loggers>
     <Logger name="metabase" level="FATAL"/>
+    <Logger name="metabase-enterprise" level="FATAL"/>
 
     <Root level="ERROR">
       <AppenderRef ref="Console"/>
diff --git a/test_resources/saml-test-response-new-user-with-groups.xml b/test_resources/saml-test-response-new-user-with-groups.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5cdc610033f08e95856ddb3bcf2d0ec28da1b20c
--- /dev/null
+++ b/test_resources/saml-test-response-new-user-with-groups.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_6643ab567200fd4a5c73" InResponseTo="_1" Version="2.0" IssueInstant="2018-07-05T18:02:53Z" Destination="http://localhost:3000/auth/sso">
+  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:saml-metabase-test.auth0.com</saml:Issuer>
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+  </samlp:Status>
+  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_vUq3VKutPOwCGartAC4Cbdmql23G3PE2" IssueInstant="2018-07-05T18:02:53.262Z">
+    <saml:Issuer>urn:saml-metabase-test.auth0.com</saml:Issuer>
+    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <SignedInfo>
+        <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+        <Reference URI="#_vUq3VKutPOwCGartAC4Cbdmql23G3PE2">
+          <Transforms>
+            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </Transforms>
+          <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+          <DigestValue>4SH+6iSi9cU3PCPqmJDnP6iK+BY=</DigestValue>
+        </Reference>
+      </SignedInfo>
+      <SignatureValue>Dvfajt2ZBDHmHGl0yLETE1trkR99Bv+kVbgVtqNTRBeeb2oUShBY9EQJKmFtdReuamqr1Tn3kjaxZefxpd1XzB4dzsWNKF0duThKP5YTg5njM3PBsN/yXFAIrIHTjedHEOdwuDgPm+endqEtaFm8yQkn0VhYGK86udpPsMvxwPbYdy2eCAWuHXpNFynfWSRJz7rckubIGvXm7Lar/bMbMYiJa5u+wVqmeH53IddjIEQOeZLsQfPyNeZolWGqBv0TzpM/yEejDjQnnj55fMFXpGS3lKJ02vArYLdgtjW2isgB8/m5dY0gjFob0k9ioy+nghdH/rDKFIvqS/Jb8A50zg==</SignatureValue>
+      <KeyInfo>
+        <X509Data>
+          <X509Certificate>MIIDEzCCAfugAwIBAgIJYpjQiNMYxf1GMA0GCSqGSIb3DQEBCwUAMCcxJTAjBgNVBAMTHHNhbWwtbWV0YWJhc2UtdGVzdC5hdXRoMC5jb20wHhcNMTgwNTI5MjEwMDIzWhcNMzIwMjA1MjEwMDIzWjAnMSUwIwYDVQQDExxzYW1sLW1ldGFiYXNlLXRlc3QuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzNcrpju4sILZQNe1adwg3beXtAMFGB+Buuc414+FDv2OG7X7b9OSYar/nsYfWwiazZRxEGriagd0Sj5mJ4Qqx+zmB/r4UgX3q/KgocRLlShvvz5gTD99hR7LonDPSWET1E9PD4XE1fRaq+BwftFBl45pKTcCR9QrUAFZJ2R/3g06NPZdhe4bg/lTssY5emCxaZpQEku/v+zzpV2nLF4by0vSj7AHsubrsLgsCfV3JvJyTxCyo1aIOlv4Vrx7h9rOgl9eEmoU5XJAl3D7DuvSTEOy7MyDnKF17m7l5nOPZCVOSzmCWvxSCyysijgsM5DSgAE8DPJyoYezV3gTX2OO2QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSpB3lvrtbSDuXkB6fhbjeUpFmL2DAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAAEHGIAhR5GPD2JxgLtpNtZMCYiAM4Gr7hoTQMaKiXgVtdQu4iMFfbpEwIr6UVaDU2HKhvSRFIilOjRGmCGrIzvJgR2l+RL1Z3KrZypI1AXKJT5pF5g5FitBsZq+kiUpdRILl2hICzw9Q1M2Le+JSUcHcbHTVgF24xuzOZonxeE56Oc26Ju4CorLpM3Nb5iYaGOlQ+48/GP82cLxlVyi02va8tp7KP03ePSaZeBEKGpFtBtEN/dC3NKO1mmrT9284H0tvete6KLUH+dsS6bDEYGHZM5KGoSLWRr3qYlCB3AmAw+KvuiuSczLg9oYBkdxlhK9zZvkjCgaLCen+0aY67A=</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </Signature>
+    <saml:Subject>
+      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">auth0|5b0dd0185d7d1617fd8065b5</saml:NameID>
+      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+        <saml:SubjectConfirmationData NotOnOrAfter="2018-07-05T19:02:53.262Z" Recipient="http://localhost:3000/auth/sso" InResponseTo="_1"/>
+      </saml:SubjectConfirmation>
+    </saml:Subject>
+    <saml:Conditions NotBefore="2018-07-05T18:02:53.262Z" NotOnOrAfter="2018-07-05T19:02:53.262Z">
+      <saml:AudienceRestriction>
+        <saml:Audience>Metabase</saml:Audience>
+      </saml:AudienceRestriction>
+    </saml:Conditions>
+    <saml:AuthnStatement AuthnInstant="2018-07-05T18:02:53.262Z" SessionIndex="_8vKjURdHz4jghbYbj48khvBkLaEeyEqc">
+      <saml:AuthnContext>
+        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
+      </saml:AuthnContext>
+    </saml:AuthnStatement>
+    <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0|5b0dd0185d7d1617fd8065b5</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">New</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">User</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">Username-Password-Authentication</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">true</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">W1R5HozFA9cnbFVbmIT8KPdVmH0pU85k</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">https://s.gravatar.com/avatar/9e18d6fcddec348d9131522c3c535646?s=480&amp;r=pg&amp;d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fry.png</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Thu Jul 05 2018 18:02:53 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Tue May 29 2018 22:11:36 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="GroupMembership" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+        <saml:AttributeValue xsi:type="xs:anyType">group_1</saml:AttributeValue>
+        <saml:AttributeValue xsi:type="xs:anyType">group_2</saml:AttributeValue>
+      </saml:Attribute>
+    </saml:AttributeStatement>
+  </saml:Assertion>
+</samlp:Response>
diff --git a/test_resources/saml-test-response-new-user-with-single-group.xml b/test_resources/saml-test-response-new-user-with-single-group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6dd0ef0eef7c5012789bec1eb3e2442571ab0cea
--- /dev/null
+++ b/test_resources/saml-test-response-new-user-with-single-group.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_6643ab567200fd4a5c73" InResponseTo="_1" Version="2.0" IssueInstant="2018-07-05T18:02:53Z" Destination="http://localhost:3000/auth/sso">
+  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:saml-metabase-test.auth0.com</saml:Issuer>
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+  </samlp:Status>
+  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_vUq3VKutPOwCGartAC4Cbdmql23G3PE2" IssueInstant="2018-07-05T18:02:53.262Z">
+    <saml:Issuer>urn:saml-metabase-test.auth0.com</saml:Issuer>
+    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <SignedInfo>
+        <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+        <Reference URI="#_vUq3VKutPOwCGartAC4Cbdmql23G3PE2">
+          <Transforms>
+            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </Transforms>
+          <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+          <DigestValue>4SH+6iSi9cU3PCPqmJDnP6iK+BY=</DigestValue>
+        </Reference>
+      </SignedInfo>
+      <SignatureValue>Dvfajt2ZBDHmHGl0yLETE1trkR99Bv+kVbgVtqNTRBeeb2oUShBY9EQJKmFtdReuamqr1Tn3kjaxZefxpd1XzB4dzsWNKF0duThKP5YTg5njM3PBsN/yXFAIrIHTjedHEOdwuDgPm+endqEtaFm8yQkn0VhYGK86udpPsMvxwPbYdy2eCAWuHXpNFynfWSRJz7rckubIGvXm7Lar/bMbMYiJa5u+wVqmeH53IddjIEQOeZLsQfPyNeZolWGqBv0TzpM/yEejDjQnnj55fMFXpGS3lKJ02vArYLdgtjW2isgB8/m5dY0gjFob0k9ioy+nghdH/rDKFIvqS/Jb8A50zg==</SignatureValue>
+      <KeyInfo>
+        <X509Data>
+          <X509Certificate>MIIDEzCCAfugAwIBAgIJYpjQiNMYxf1GMA0GCSqGSIb3DQEBCwUAMCcxJTAjBgNVBAMTHHNhbWwtbWV0YWJhc2UtdGVzdC5hdXRoMC5jb20wHhcNMTgwNTI5MjEwMDIzWhcNMzIwMjA1MjEwMDIzWjAnMSUwIwYDVQQDExxzYW1sLW1ldGFiYXNlLXRlc3QuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzNcrpju4sILZQNe1adwg3beXtAMFGB+Buuc414+FDv2OG7X7b9OSYar/nsYfWwiazZRxEGriagd0Sj5mJ4Qqx+zmB/r4UgX3q/KgocRLlShvvz5gTD99hR7LonDPSWET1E9PD4XE1fRaq+BwftFBl45pKTcCR9QrUAFZJ2R/3g06NPZdhe4bg/lTssY5emCxaZpQEku/v+zzpV2nLF4by0vSj7AHsubrsLgsCfV3JvJyTxCyo1aIOlv4Vrx7h9rOgl9eEmoU5XJAl3D7DuvSTEOy7MyDnKF17m7l5nOPZCVOSzmCWvxSCyysijgsM5DSgAE8DPJyoYezV3gTX2OO2QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSpB3lvrtbSDuXkB6fhbjeUpFmL2DAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAAEHGIAhR5GPD2JxgLtpNtZMCYiAM4Gr7hoTQMaKiXgVtdQu4iMFfbpEwIr6UVaDU2HKhvSRFIilOjRGmCGrIzvJgR2l+RL1Z3KrZypI1AXKJT5pF5g5FitBsZq+kiUpdRILl2hICzw9Q1M2Le+JSUcHcbHTVgF24xuzOZonxeE56Oc26Ju4CorLpM3Nb5iYaGOlQ+48/GP82cLxlVyi02va8tp7KP03ePSaZeBEKGpFtBtEN/dC3NKO1mmrT9284H0tvete6KLUH+dsS6bDEYGHZM5KGoSLWRr3qYlCB3AmAw+KvuiuSczLg9oYBkdxlhK9zZvkjCgaLCen+0aY67A=</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </Signature>
+    <saml:Subject>
+      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">auth0|5b0dd0185d7d1617fd8065b5</saml:NameID>
+      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+        <saml:SubjectConfirmationData NotOnOrAfter="2018-07-05T19:02:53.262Z" Recipient="http://localhost:3000/auth/sso" InResponseTo="_1"/>
+      </saml:SubjectConfirmation>
+    </saml:Subject>
+    <saml:Conditions NotBefore="2018-07-05T18:02:53.262Z" NotOnOrAfter="2018-07-05T19:02:53.262Z">
+      <saml:AudienceRestriction>
+        <saml:Audience>Metabase</saml:Audience>
+      </saml:AudienceRestriction>
+    </saml:Conditions>
+    <saml:AuthnStatement AuthnInstant="2018-07-05T18:02:53.262Z" SessionIndex="_8vKjURdHz4jghbYbj48khvBkLaEeyEqc">
+      <saml:AuthnContext>
+        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
+      </saml:AuthnContext>
+    </saml:AuthnStatement>
+    <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0|5b0dd0185d7d1617fd8065b5</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">New</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">User</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">Username-Password-Authentication</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">true</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">W1R5HozFA9cnbFVbmIT8KPdVmH0pU85k</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">https://s.gravatar.com/avatar/9e18d6fcddec348d9131522c3c535646?s=480&amp;r=pg&amp;d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fry.png</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Thu Jul 05 2018 18:02:53 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Tue May 29 2018 22:11:36 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="GroupMembership" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
+        <saml:AttributeValue xsi:type="xs:anyType">group_1</saml:AttributeValue>
+      </saml:Attribute>
+    </saml:AttributeStatement>
+  </saml:Assertion>
+</samlp:Response>
diff --git a/test_resources/saml-test-response-new-user.xml b/test_resources/saml-test-response-new-user.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ce6534be799613f13b510dd39ddebd073bcb7445
--- /dev/null
+++ b/test_resources/saml-test-response-new-user.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_6643ab567200fd4a5c73" InResponseTo="_1" Version="2.0" IssueInstant="2018-07-05T18:02:53Z" Destination="http://localhost:3000/auth/sso">
+  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:saml-metabase-test.auth0.com</saml:Issuer>
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+  </samlp:Status>
+  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_vUq3VKutPOwCGartAC4Cbdmql23G3PE2" IssueInstant="2018-07-05T18:02:53.262Z">
+    <saml:Issuer>urn:saml-metabase-test.auth0.com</saml:Issuer>
+    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <SignedInfo>
+        <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+        <Reference URI="#_vUq3VKutPOwCGartAC4Cbdmql23G3PE2">
+          <Transforms>
+            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </Transforms>
+          <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+          <DigestValue>4SH+6iSi9cU3PCPqmJDnP6iK+BY=</DigestValue>
+        </Reference>
+      </SignedInfo>
+      <SignatureValue>Dvfajt2ZBDHmHGl0yLETE1trkR99Bv+kVbgVtqNTRBeeb2oUShBY9EQJKmFtdReuamqr1Tn3kjaxZefxpd1XzB4dzsWNKF0duThKP5YTg5njM3PBsN/yXFAIrIHTjedHEOdwuDgPm+endqEtaFm8yQkn0VhYGK86udpPsMvxwPbYdy2eCAWuHXpNFynfWSRJz7rckubIGvXm7Lar/bMbMYiJa5u+wVqmeH53IddjIEQOeZLsQfPyNeZolWGqBv0TzpM/yEejDjQnnj55fMFXpGS3lKJ02vArYLdgtjW2isgB8/m5dY0gjFob0k9ioy+nghdH/rDKFIvqS/Jb8A50zg==</SignatureValue>
+      <KeyInfo>
+        <X509Data>
+          <X509Certificate>MIIDEzCCAfugAwIBAgIJYpjQiNMYxf1GMA0GCSqGSIb3DQEBCwUAMCcxJTAjBgNVBAMTHHNhbWwtbWV0YWJhc2UtdGVzdC5hdXRoMC5jb20wHhcNMTgwNTI5MjEwMDIzWhcNMzIwMjA1MjEwMDIzWjAnMSUwIwYDVQQDExxzYW1sLW1ldGFiYXNlLXRlc3QuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzNcrpju4sILZQNe1adwg3beXtAMFGB+Buuc414+FDv2OG7X7b9OSYar/nsYfWwiazZRxEGriagd0Sj5mJ4Qqx+zmB/r4UgX3q/KgocRLlShvvz5gTD99hR7LonDPSWET1E9PD4XE1fRaq+BwftFBl45pKTcCR9QrUAFZJ2R/3g06NPZdhe4bg/lTssY5emCxaZpQEku/v+zzpV2nLF4by0vSj7AHsubrsLgsCfV3JvJyTxCyo1aIOlv4Vrx7h9rOgl9eEmoU5XJAl3D7DuvSTEOy7MyDnKF17m7l5nOPZCVOSzmCWvxSCyysijgsM5DSgAE8DPJyoYezV3gTX2OO2QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSpB3lvrtbSDuXkB6fhbjeUpFmL2DAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAAEHGIAhR5GPD2JxgLtpNtZMCYiAM4Gr7hoTQMaKiXgVtdQu4iMFfbpEwIr6UVaDU2HKhvSRFIilOjRGmCGrIzvJgR2l+RL1Z3KrZypI1AXKJT5pF5g5FitBsZq+kiUpdRILl2hICzw9Q1M2Le+JSUcHcbHTVgF24xuzOZonxeE56Oc26Ju4CorLpM3Nb5iYaGOlQ+48/GP82cLxlVyi02va8tp7KP03ePSaZeBEKGpFtBtEN/dC3NKO1mmrT9284H0tvete6KLUH+dsS6bDEYGHZM5KGoSLWRr3qYlCB3AmAw+KvuiuSczLg9oYBkdxlhK9zZvkjCgaLCen+0aY67A=</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </Signature>
+    <saml:Subject>
+      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">auth0|5b0dd0185d7d1617fd8065b5</saml:NameID>
+      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+        <saml:SubjectConfirmationData NotOnOrAfter="2018-07-05T19:02:53.262Z" Recipient="http://localhost:3000/auth/sso" InResponseTo="_1"/>
+      </saml:SubjectConfirmation>
+    </saml:Subject>
+    <saml:Conditions NotBefore="2018-07-05T18:02:53.262Z" NotOnOrAfter="2018-07-05T19:02:53.262Z">
+      <saml:AudienceRestriction>
+        <saml:Audience>Metabase</saml:Audience>
+      </saml:AudienceRestriction>
+    </saml:Conditions>
+    <saml:AuthnStatement AuthnInstant="2018-07-05T18:02:53.262Z" SessionIndex="_8vKjURdHz4jghbYbj48khvBkLaEeyEqc">
+      <saml:AuthnContext>
+        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
+      </saml:AuthnContext>
+    </saml:AuthnStatement>
+    <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0|5b0dd0185d7d1617fd8065b5</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">New</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">User</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">Username-Password-Authentication</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">true</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">W1R5HozFA9cnbFVbmIT8KPdVmH0pU85k</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">https://s.gravatar.com/avatar/9e18d6fcddec348d9131522c3c535646?s=480&amp;r=pg&amp;d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fry.png</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">newuser</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Thu Jul 05 2018 18:02:53 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Tue May 29 2018 22:11:36 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+    </saml:AttributeStatement>
+  </saml:Assertion>
+</samlp:Response>
diff --git a/test_resources/saml-test-response.xml b/test_resources/saml-test-response.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e392ece57f94871fb34d76cd4988fa38e2eb21a1
--- /dev/null
+++ b/test_resources/saml-test-response.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_6643ab567200fd4a5c73" InResponseTo="_1" Version="2.0" IssueInstant="2018-07-05T18:02:53Z" Destination="http://localhost:3000/auth/sso">
+  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:saml-metabase-test.auth0.com</saml:Issuer>
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+  </samlp:Status>
+  <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_vUq3VKutPOwCGartAC4Cbdmql23G3PE2" IssueInstant="2018-07-05T18:02:53.262Z">
+    <saml:Issuer>urn:saml-metabase-test.auth0.com</saml:Issuer>
+    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <SignedInfo>
+        <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+        <Reference URI="#_vUq3VKutPOwCGartAC4Cbdmql23G3PE2">
+          <Transforms>
+            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </Transforms>
+          <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
+          <DigestValue>4SH+6iSi9cU3PCPqmJDnP6iK+BY=</DigestValue>
+        </Reference>
+      </SignedInfo>
+      <SignatureValue>Dvfajt2ZBDHmHGl0yLETE1trkR99Bv+kVbgVtqNTRBeeb2oUShBY9EQJKmFtdReuamqr1Tn3kjaxZefxpd1XzB4dzsWNKF0duThKP5YTg5njM3PBsN/yXFAIrIHTjedHEOdwuDgPm+endqEtaFm8yQkn0VhYGK86udpPsMvxwPbYdy2eCAWuHXpNFynfWSRJz7rckubIGvXm7Lar/bMbMYiJa5u+wVqmeH53IddjIEQOeZLsQfPyNeZolWGqBv0TzpM/yEejDjQnnj55fMFXpGS3lKJ02vArYLdgtjW2isgB8/m5dY0gjFob0k9ioy+nghdH/rDKFIvqS/Jb8A50zg==</SignatureValue>
+      <KeyInfo>
+        <X509Data>
+          <X509Certificate>MIIDEzCCAfugAwIBAgIJYpjQiNMYxf1GMA0GCSqGSIb3DQEBCwUAMCcxJTAjBgNVBAMTHHNhbWwtbWV0YWJhc2UtdGVzdC5hdXRoMC5jb20wHhcNMTgwNTI5MjEwMDIzWhcNMzIwMjA1MjEwMDIzWjAnMSUwIwYDVQQDExxzYW1sLW1ldGFiYXNlLXRlc3QuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzNcrpju4sILZQNe1adwg3beXtAMFGB+Buuc414+FDv2OG7X7b9OSYar/nsYfWwiazZRxEGriagd0Sj5mJ4Qqx+zmB/r4UgX3q/KgocRLlShvvz5gTD99hR7LonDPSWET1E9PD4XE1fRaq+BwftFBl45pKTcCR9QrUAFZJ2R/3g06NPZdhe4bg/lTssY5emCxaZpQEku/v+zzpV2nLF4by0vSj7AHsubrsLgsCfV3JvJyTxCyo1aIOlv4Vrx7h9rOgl9eEmoU5XJAl3D7DuvSTEOy7MyDnKF17m7l5nOPZCVOSzmCWvxSCyysijgsM5DSgAE8DPJyoYezV3gTX2OO2QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSpB3lvrtbSDuXkB6fhbjeUpFmL2DAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAAEHGIAhR5GPD2JxgLtpNtZMCYiAM4Gr7hoTQMaKiXgVtdQu4iMFfbpEwIr6UVaDU2HKhvSRFIilOjRGmCGrIzvJgR2l+RL1Z3KrZypI1AXKJT5pF5g5FitBsZq+kiUpdRILl2hICzw9Q1M2Le+JSUcHcbHTVgF24xuzOZonxeE56Oc26Ju4CorLpM3Nb5iYaGOlQ+48/GP82cLxlVyi02va8tp7KP03ePSaZeBEKGpFtBtEN/dC3NKO1mmrT9284H0tvete6KLUH+dsS6bDEYGHZM5KGoSLWRr3qYlCB3AmAw+KvuiuSczLg9oYBkdxlhK9zZvkjCgaLCen+0aY67A=</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </Signature>
+    <saml:Subject>
+      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">auth0|5b0dd0185d7d1617fd8065b5</saml:NameID>
+      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+        <saml:SubjectConfirmationData NotOnOrAfter="2018-07-05T19:02:53.262Z" Recipient="http://localhost:3000/auth/sso" InResponseTo="_1"/>
+      </saml:SubjectConfirmation>
+    </saml:Subject>
+    <saml:Conditions NotBefore="2018-07-05T18:02:53.262Z" NotOnOrAfter="2018-07-05T19:02:53.262Z">
+      <saml:AudienceRestriction>
+        <saml:Audience>Metabase</saml:Audience>
+      </saml:AudienceRestriction>
+    </saml:Conditions>
+    <saml:AuthnStatement AuthnInstant="2018-07-05T18:02:53.262Z" SessionIndex="_8vKjURdHz4jghbYbj48khvBkLaEeyEqc">
+      <saml:AuthnContext>
+        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
+      </saml:AuthnContext>
+    </saml:AuthnStatement>
+    <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0|5b0dd0185d7d1617fd8065b5</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">rasta@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">rasta@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">rasta@metabase.com</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/provider" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">auth0</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/connection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">Username-Password-Authentication</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/identities/default/isSocial" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">false</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/email_verified" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:boolean">true</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/clientID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">W1R5HozFA9cnbFVbmIT8KPdVmH0pU85k</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/picture" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">https://s.gravatar.com/avatar/9e18d6fcddec348d9131522c3c535646?s=480&amp;r=pg&amp;d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fry.png</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/nickname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:string">rasta</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/updated_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Thu Jul 05 2018 18:02:53 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+      <saml:Attribute Name="http://schemas.auth0.com/created_at" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xsi:type="xs:anyType">Tue May 29 2018 22:11:36 GMT+0000 (UTC)</saml:AttributeValue>
+      </saml:Attribute>
+    </saml:AttributeStatement>
+  </saml:Assertion>
+</samlp:Response>
diff --git a/version.json b/version.json
new file mode 100644
index 0000000000000000000000000000000000000000..c5a5e4e414679069752307e7f24762e4c3d6901d
--- /dev/null
+++ b/version.json
@@ -0,0 +1,3 @@
+{
+  "edition": "enterprise"
+}
diff --git a/webpack.config.js b/webpack.config.js
index 9157090f498a9324e3ac24f73d4e6cdb729b698a..7a7e1dc65691020839a08c80b1b996ca615de74c 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -21,6 +21,8 @@ const ASSETS_PATH = __dirname + "/resources/frontend_client/app/assets";
 const FONTS_PATH = __dirname + "/resources/frontend_client/app/fonts";
 const SRC_PATH = __dirname + "/frontend/src/metabase";
 const LIB_SRC_PATH = __dirname + "/frontend/src/metabase-lib";
+const ENTERPRISE_SRC_PATH =
+  __dirname + "/enterprise/frontend/src/metabase-enterprise";
 const TYPES_SRC_PATH = __dirname + "/frontend/src/metabase-types";
 const TEST_SUPPORT_PATH = __dirname + "/frontend/test/__support__";
 const BUILD_PATH = __dirname + "/resources/frontend_client";
@@ -104,6 +106,7 @@ const config = (module.exports = {
       fonts: FONTS_PATH,
       metabase: SRC_PATH,
       "metabase-lib": LIB_SRC_PATH,
+      "metabase-enterprise": ENTERPRISE_SRC_PATH,
       "metabase-types": TYPES_SRC_PATH,
       __support__: TEST_SUPPORT_PATH,
       style: SRC_PATH + "/css/core/index",
@@ -112,6 +115,11 @@ const config = (module.exports = {
       // icepick 2.x is es6 by defalt, to maintain backwards compatability
       // with ie11 point to the minified version
       icepick: __dirname + "/node_modules/icepick/icepick.min",
+      // conditionally load either the EE plugins file or a empty file in the CE code tree
+      "ee-plugins":
+        process.env.MB_EDITION === "ENTERPRISE"
+          ? ENTERPRISE_SRC_PATH + "/plugins"
+          : SRC_PATH + "/lib/noop",
     },
   },
 
@@ -169,9 +177,8 @@ const config = (module.exports = {
       outputPath: __dirname + "/resources/frontend_client/app/dist",
     }),
     new webpack.DefinePlugin({
-      "process.env": {
-        NODE_ENV: JSON.stringify(NODE_ENV),
-      },
+      "process.env": { NODE_ENV: JSON.stringify(NODE_ENV) },
+      INCLUDE_EE_PLUGINS: JSON.stringify(process.env.MB_EDITION === "ee"),
     }),
     new BannerWebpackPlugin({
       chunks: {
@@ -253,11 +260,11 @@ if (NODE_ENV !== "production") {
     }
   }
 
-  // enable "cheap" source maps in hot or watch mode since re-build speed overhead is < 1 second
-  // config.devtool = "cheap-module-source-map";
-
-  // works with breakpoints and makes stacktraces readable
-  config.devtool = "inline-module-source-map";
+  // by default enable "cheap" source maps for fast re-build speed
+  // with BETTER_SOURCE_MAPS we switch to sourcemaps that work with breakpoints and makes stacktraces readable
+  config.devtool = process.env.BETTER_SOURCE_MAPS
+    ? "inline-module-source-map"
+    : "cheap-module-source-map";
 
   // helps with source maps
   config.output.devtoolModuleFilenameTemplate = "[absolute-resource-path]";
diff --git a/yarn.lock b/yarn.lock
index f834589252c0e0533c89ddd7a83e2462a7a78324..e71b7d811af63404b37f7467119b137342eab991 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,371 +2,249 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
-  integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
-  dependencies:
-    "@babel/highlight" "^7.0.0"
-
-"@babel/code-frame@^7.10.4":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
   integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
   dependencies:
     "@babel/highlight" "^7.10.4"
 
-"@babel/code-frame@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
-  integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
-  dependencies:
-    "@babel/highlight" "^7.0.0"
-
-"@babel/core@^7.0.1":
-  version "7.7.2"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.2.tgz#ea5b99693bcfc058116f42fa1dd54da412b29d91"
-  integrity sha512-eeD7VEZKfhK1KUXGiyPFettgF3m513f8FoBSWiQ1xTvl1RAopLs42Wp9+Ze911I6H0N9lNqJMDgoZT7gHsipeQ==
-  dependencies:
-    "@babel/code-frame" "^7.5.5"
-    "@babel/generator" "^7.7.2"
-    "@babel/helpers" "^7.7.0"
-    "@babel/parser" "^7.7.2"
-    "@babel/template" "^7.7.0"
-    "@babel/traverse" "^7.7.2"
-    "@babel/types" "^7.7.2"
-    convert-source-map "^1.7.0"
-    debug "^4.1.0"
-    json5 "^2.1.0"
-    lodash "^4.17.13"
-    resolve "^1.3.2"
-    semver "^5.4.1"
-    source-map "^0.5.0"
+"@babel/compat-data@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.1.tgz#d7386a689aa0ddf06255005b4b991988021101a0"
+  integrity sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ==
 
-"@babel/core@^7.4.4":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a"
-  integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==
+"@babel/core@^7.0.1", "@babel/core@^7.8.4":
+  version "7.12.3"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8"
+  integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.4.4"
-    "@babel/helpers" "^7.4.4"
-    "@babel/parser" "^7.4.5"
-    "@babel/template" "^7.4.4"
-    "@babel/traverse" "^7.4.5"
-    "@babel/types" "^7.4.4"
-    convert-source-map "^1.1.0"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.12.1"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helpers" "^7.12.1"
+    "@babel/parser" "^7.12.3"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.12.1"
+    "@babel/types" "^7.12.1"
+    convert-source-map "^1.7.0"
     debug "^4.1.0"
-    json5 "^2.1.0"
-    lodash "^4.17.11"
+    gensync "^1.0.0-beta.1"
+    json5 "^2.1.2"
+    lodash "^4.17.19"
     resolve "^1.3.2"
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
-  integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
+"@babel/generator@^7.12.1", "@babel/generator@^7.9.5":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468"
+  integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==
   dependencies:
-    "@babel/types" "^7.4.4"
-    jsesc "^2.5.1"
-    lodash "^4.17.11"
-    source-map "^0.5.0"
-    trim-right "^1.0.1"
-
-"@babel/generator@^7.7.2":
-  version "7.7.2"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.2.tgz#2f4852d04131a5e17ea4f6645488b5da66ebf3af"
-  integrity sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==
-  dependencies:
-    "@babel/types" "^7.7.2"
-    jsesc "^2.5.1"
-    lodash "^4.17.13"
-    source-map "^0.5.0"
-
-"@babel/generator@^7.9.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69"
-  integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==
-  dependencies:
-    "@babel/types" "^7.10.5"
+    "@babel/types" "^7.12.1"
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
-"@babel/helper-annotate-as-pure@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
-  integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-annotate-as-pure@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.0.tgz#efc54032d43891fe267679e63f6860aa7dbf4a5e"
-  integrity sha512-k50CQxMlYTYo+GGyUGFwpxKVtxVJi9yh61sXZji3zYHccK9RYliZGSTOgci85T+r+0VFN2nWbGM04PIqwfrpMg==
-  dependencies:
-    "@babel/types" "^7.7.0"
-
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.0.tgz#32dd9551d6ed3a5fc2edc50d6912852aa18274d9"
-  integrity sha512-Cd8r8zs4RKDwMG/92lpZcnn5WPQ3LAMQbCw42oqUh4s7vsSN5ANUZjMel0OOnxDLq57hoDDbai+ryygYfCTOsw==
+"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
+  integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
   dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-call-delegate@^7.4.4":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.7.0.tgz#df8942452c2c1a217335ca7e393b9afc67f668dc"
-  integrity sha512-Su0Mdq7uSSWGZayGMMQ+z6lnL00mMCnGAbO/R0ZO9odIdB/WNU/VfQKqMQU0fdIsxQYbRjDM4BixIa93SQIpvw==
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3"
+  integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.7.0"
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/helper-explode-assignable-expression" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-create-regexp-features-plugin@^7.7.0":
-  version "7.7.2"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.2.tgz#6f20443778c8fce2af2ff4206284afc0ced65db6"
-  integrity sha512-pAil/ZixjTlrzNpjx+l/C/wJk002Wo7XbbZ8oujH/AoJ3Juv0iN/UTcPUHXKMFLqsfS0Hy6Aow8M31brUYBlQQ==
+"@babel/helper-compilation-targets@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz#310e352888fbdbdd8577be8dfdd2afb9e7adcf50"
+  integrity sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g==
   dependencies:
-    "@babel/helper-regex" "^7.4.4"
-    regexpu-core "^4.6.0"
+    "@babel/compat-data" "^7.12.1"
+    "@babel/helper-validator-option" "^7.12.1"
+    browserslist "^4.12.0"
+    semver "^5.5.0"
 
-"@babel/helper-define-map@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
-  integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==
+"@babel/helper-create-class-features-plugin@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz#3c45998f431edd4a9214c5f1d3ad1448a6137f6e"
+  integrity sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==
   dependencies:
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/types" "^7.4.4"
-    lodash "^4.17.11"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-member-expression-to-functions" "^7.12.1"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.12.1"
+    "@babel/helper-split-export-declaration" "^7.10.4"
 
-"@babel/helper-define-map@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.0.tgz#60b0e9fd60def9de5054c38afde8c8ee409c7529"
-  integrity sha512-kPKWPb0dMpZi+ov1hJiwse9dWweZsz3V9rP4KdytnX1E7z3cTNmFGglwklzFPuqIcHLIY3bgKSs4vkwXXdflQA==
+"@babel/helper-create-regexp-features-plugin@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz#18b1302d4677f9dc4740fe8c9ed96680e29d37e8"
+  integrity sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==
   dependencies:
-    "@babel/helper-function-name" "^7.7.0"
-    "@babel/types" "^7.7.0"
-    lodash "^4.17.13"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
+    regexpu-core "^4.7.1"
 
-"@babel/helper-explode-assignable-expression@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.0.tgz#db2a6705555ae1f9f33b4b8212a546bc7f9dc3ef"
-  integrity sha512-CDs26w2shdD1urNUAji2RJXyBFCaR+iBEGnFz3l7maizMkQe3saVw9WtjG1tz8CwbjvlFnaSLVhgnu1SWaherg==
+"@babel/helper-define-map@^7.10.4":
+  version "7.10.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30"
+  integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==
   dependencies:
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/types" "^7.10.5"
+    lodash "^4.17.19"
 
-"@babel/helper-function-name@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53"
-  integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==
+"@babel/helper-explode-assignable-expression@^7.10.4":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633"
+  integrity sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.0.0"
-    "@babel/template" "^7.1.0"
-    "@babel/types" "^7.0.0"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-function-name@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz#44a5ad151cfff8ed2599c91682dda2ec2c8430a3"
-  integrity sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==
+"@babel/helper-function-name@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+  integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.7.0"
-    "@babel/template" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/helper-get-function-arity" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-get-function-arity@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3"
-  integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==
+"@babel/helper-get-function-arity@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+  integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
   dependencies:
-    "@babel/types" "^7.0.0"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-get-function-arity@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz#c604886bc97287a1d1398092bc666bc3d7d7aa2d"
-  integrity sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==
+"@babel/helper-hoist-variables@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
+  integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==
   dependencies:
-    "@babel/types" "^7.7.0"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-hoist-variables@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.0.tgz#b4552e4cfe5577d7de7b183e193e84e4ec538c81"
-  integrity sha512-LUe/92NqsDAkJjjCEWkNe+/PcpnisvnqdlRe19FahVapa4jndeuJ+FBiTX1rcAKWKcJGE+C3Q3tuEuxkSmCEiQ==
+"@babel/helper-member-expression-to-functions@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c"
+  integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==
   dependencies:
-    "@babel/types" "^7.7.0"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-member-expression-to-functions@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f"
-  integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz#1644c01591a15a2f084dd6d092d9430eb1d1216c"
+  integrity sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==
   dependencies:
-    "@babel/types" "^7.0.0"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-member-expression-to-functions@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.0.tgz#472b93003a57071f95a541ea6c2b098398bcad8a"
-  integrity sha512-QaCZLO2RtBcmvO/ekOLp8p7R5X2JriKRizeDpm5ChATAFWrrYDcDxPuCIBXKyBjY+i1vYSdcUTMIb8psfxHDPA==
+"@babel/helper-module-transforms@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c"
+  integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==
   dependencies:
-    "@babel/types" "^7.7.0"
+    "@babel/helper-module-imports" "^7.12.1"
+    "@babel/helper-replace-supers" "^7.12.1"
+    "@babel/helper-simple-access" "^7.12.1"
+    "@babel/helper-split-export-declaration" "^7.11.0"
+    "@babel/helper-validator-identifier" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.12.1"
+    "@babel/types" "^7.12.1"
+    lodash "^4.17.19"
 
-"@babel/helper-module-imports@^7.0.0":
+"@babel/helper-optimise-call-expression@^7.10.4":
   version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
-  integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
+  integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
   dependencies:
     "@babel/types" "^7.10.4"
 
-"@babel/helper-module-imports@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.0.tgz#99c095889466e5f7b6d66d98dffc58baaf42654d"
-  integrity sha512-Dv3hLKIC1jyfTkClvyEkYP2OlkzNvWs5+Q8WgPbxM5LMeorons7iPP91JM+DU7tRbhqA1ZeooPaMFvQrn23RHw==
-  dependencies:
-    "@babel/types" "^7.7.0"
-
-"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.0.tgz#154a69f0c5b8fd4d39e49750ff7ac4faa3f36786"
-  integrity sha512-rXEefBuheUYQyX4WjV19tuknrJFwyKw0HgzRwbkyTbB+Dshlq7eqkWbyjzToLrMZk/5wKVKdWFluiAsVkHXvuQ==
-  dependencies:
-    "@babel/helper-module-imports" "^7.7.0"
-    "@babel/helper-simple-access" "^7.7.0"
-    "@babel/helper-split-export-declaration" "^7.7.0"
-    "@babel/template" "^7.7.0"
-    "@babel/types" "^7.7.0"
-    lodash "^4.17.13"
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+  integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
 
-"@babel/helper-optimise-call-expression@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5"
-  integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==
+"@babel/helper-regex@^7.10.4":
+  version "7.10.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0"
+  integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==
   dependencies:
-    "@babel/types" "^7.0.0"
+    lodash "^4.17.19"
 
-"@babel/helper-optimise-call-expression@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.0.tgz#4f66a216116a66164135dc618c5d8b7a959f9365"
-  integrity sha512-48TeqmbazjNU/65niiiJIJRc5JozB8acui1OS7bSd6PgxfuovWsvjfWSzlgx+gPFdVveNzUdpdIg5l56Pl5jqg==
+"@babel/helper-remap-async-to-generator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd"
+  integrity sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==
   dependencies:
-    "@babel/types" "^7.7.0"
-
-"@babel/helper-plugin-utils@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
-  integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
-
-"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351"
-  integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==
-  dependencies:
-    lodash "^4.17.13"
-
-"@babel/helper-remap-async-to-generator@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.0.tgz#4d69ec653e8bff5bce62f5d33fc1508f223c75a7"
-  integrity sha512-pHx7RN8X0UNHPB/fnuDnRXVZ316ZigkO8y8D835JlZ2SSdFKb6yH9MIYRU4fy/KPe5sPHDFOPvf8QLdbAGGiyw==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.7.0"
-    "@babel/helper-wrap-function" "^7.7.0"
-    "@babel/template" "^7.7.0"
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
-
-"@babel/helper-replace-supers@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
-  integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
-  dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.0.0"
-    "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/traverse" "^7.4.4"
-    "@babel/types" "^7.4.4"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-wrap-function" "^7.10.4"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-replace-supers@^7.5.5", "@babel/helper-replace-supers@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.0.tgz#d5365c8667fe7cbd13b8ddddceb9bd7f2b387512"
-  integrity sha512-5ALYEul5V8xNdxEeWvRsBzLMxQksT7MaStpxjJf9KsnLxpAKBtfw5NeMKZJSYDa0lKdOcy0g+JT/f5mPSulUgg==
+"@babel/helper-replace-supers@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9"
+  integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.7.0"
-    "@babel/helper-optimise-call-expression" "^7.7.0"
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/helper-member-expression-to-functions" "^7.12.1"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/traverse" "^7.12.1"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-simple-access@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.0.tgz#97a8b6c52105d76031b86237dc1852b44837243d"
-  integrity sha512-AJ7IZD7Eem3zZRuj5JtzFAptBw7pMlS3y8Qv09vaBWoFsle0d1kAn5Wq6Q9MyBXITPOKnxwkZKoAm4bopmv26g==
+"@babel/helper-simple-access@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136"
+  integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==
   dependencies:
-    "@babel/template" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-split-export-declaration@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
-  integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
+"@babel/helper-skip-transparent-expression-wrappers@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf"
+  integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==
   dependencies:
-    "@babel/types" "^7.4.4"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-split-export-declaration@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz#1365e74ea6c614deeb56ebffabd71006a0eb2300"
-  integrity sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==
+"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0":
+  version "7.11.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
+  integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
   dependencies:
-    "@babel/types" "^7.7.0"
+    "@babel/types" "^7.11.0"
 
 "@babel/helper-validator-identifier@^7.10.4":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
   integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
 
-"@babel/helper-wrap-function@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa"
-  integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==
-  dependencies:
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/template" "^7.1.0"
-    "@babel/traverse" "^7.1.0"
-    "@babel/types" "^7.2.0"
-
-"@babel/helper-wrap-function@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.0.tgz#15af3d3e98f8417a60554acbb6c14e75e0b33b74"
-  integrity sha512-sd4QjeMgQqzshSjecZjOp8uKfUtnpmCyQhKQrVJBBgeHAB/0FPi33h3AbVlVp07qQtMD4QgYSzaMI7VwncNK/w==
-  dependencies:
-    "@babel/helper-function-name" "^7.7.0"
-    "@babel/template" "^7.7.0"
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
-
-"@babel/helpers@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5"
-  integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==
-  dependencies:
-    "@babel/template" "^7.4.4"
-    "@babel/traverse" "^7.4.4"
-    "@babel/types" "^7.4.4"
+"@babel/helper-validator-option@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9"
+  integrity sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==
 
-"@babel/helpers@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.0.tgz#359bb5ac3b4726f7c1fde0ec75f64b3f4275d60b"
-  integrity sha512-VnNwL4YOhbejHb7x/b5F39Zdg5vIQpUUNzJwx0ww1EcVRt41bbGRZWhAURrfY32T5zTT3qwNOQFWpn+P0i0a2g==
+"@babel/helper-wrap-function@^7.10.4":
+  version "7.12.3"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz#3332339fc4d1fbbf1c27d7958c27d34708e990d9"
+  integrity sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==
   dependencies:
-    "@babel/template" "^7.7.0"
-    "@babel/traverse" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/highlight@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
-  integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==
+"@babel/helpers@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.1.tgz#8a8261c1d438ec18cb890434df4ec768734c1e79"
+  integrity sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==
   dependencies:
-    chalk "^2.0.0"
-    esutils "^2.0.2"
-    js-tokens "^4.0.0"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.12.1"
+    "@babel/types" "^7.12.1"
 
 "@babel/highlight@^7.10.4":
   version "7.10.4"
@@ -377,511 +255,572 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.10.4":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b"
-  integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==
+"@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3":
+  version "7.12.3"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd"
+  integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==
 
-"@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
-  integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
+"@babel/plugin-proposal-async-generator-functions@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e"
+  integrity sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.12.1"
+    "@babel/plugin-syntax-async-generators" "^7.8.0"
+
+"@babel/plugin-proposal-class-properties@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de"
+  integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/parser@^7.7.0", "@babel/parser@^7.7.2":
-  version "7.7.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.3.tgz#5fad457c2529de476a248f75b0f090b3060af043"
-  integrity sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==
+"@babel/plugin-proposal-dynamic-import@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc"
+  integrity sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.0"
 
-"@babel/plugin-proposal-async-generator-functions@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.0.tgz#83ef2d6044496b4c15d8b4904e2219e6dccc6971"
-  integrity sha512-ot/EZVvf3mXtZq0Pd0+tSOfGWMizqmOohXmNZg6LNFjHOV+wOPv7BvVYh8oPR8LhpIP3ye8nNooKL50YRWxpYA==
+"@babel/plugin-proposal-export-namespace-from@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz#8b9b8f376b2d88f5dd774e4d24a5cc2e3679b6d4"
+  integrity sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-remap-async-to-generator" "^7.7.0"
-    "@babel/plugin-syntax-async-generators" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
 
-"@babel/plugin-proposal-dynamic-import@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.0.tgz#dc02a8bad8d653fb59daf085516fa416edd2aa7f"
-  integrity sha512-7poL3Xi+QFPC7sGAzEIbXUyYzGJwbc2+gSD0AkiC5k52kH2cqHdqxm5hNFfLW3cRSTcx9bN0Fl7/6zWcLLnKAQ==
+"@babel/plugin-proposal-function-sent@^7.8.3":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.12.1.tgz#8f302aa8ab5f00af6c94a3e1108fa35d15319510"
+  integrity sha512-EXB01ACyNW0WCffP4ip40TH82X86+U0dakFZjyiMpoZ8NFmL5MMARzVBzy+Gg59B6vTgfvIhRHUhe6tNUw+vjw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-wrap-function" "^7.10.4"
+    "@babel/plugin-syntax-function-sent" "^7.12.1"
 
-"@babel/plugin-proposal-function-sent@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.2.0.tgz#f707d78551f49162e152d477fba32357341915d1"
-  integrity sha512-qQBDKRSCu1wGJi3jbngs18vrujVQA4F+OkSuIQYRhE6y19jcPzeEIGOc683mCQXDUR3BQCz8JyCupIwv+IRFmA==
+"@babel/plugin-proposal-json-strings@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz#d45423b517714eedd5621a9dfdc03fa9f4eb241c"
+  integrity sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-wrap-function" "^7.2.0"
-    "@babel/plugin-syntax-function-sent" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-json-strings" "^7.8.0"
 
-"@babel/plugin-proposal-json-strings@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
-  integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
+"@babel/plugin-proposal-logical-assignment-operators@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz#f2c490d36e1b3c9659241034a5d2cd50263a2751"
+  integrity sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-json-strings" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
 
-"@babel/plugin-proposal-object-rest-spread@^7.6.2":
-  version "7.6.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096"
-  integrity sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c"
+  integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
 
-"@babel/plugin-proposal-optional-catch-binding@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5"
-  integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==
+"@babel/plugin-proposal-numeric-separator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz#0e2c6774c4ce48be412119b4d693ac777f7685a6"
+  integrity sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-unicode-property-regex@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.0.tgz#549fe1717a1bd0a2a7e63163841cb37e78179d5d"
-  integrity sha512-mk34H+hp7kRBWJOOAR0ZMGCydgKMD4iN9TpDRp3IIcbunltxEY89XSimc6WbtSLCDrwcdy/EEw7h5CFCzxTchw==
+"@babel/plugin-proposal-object-rest-spread@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069"
+  integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/plugin-transform-parameters" "^7.12.1"
 
-"@babel/plugin-syntax-async-generators@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f"
-  integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==
+"@babel/plugin-proposal-optional-catch-binding@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz#ccc2421af64d3aae50b558a71cede929a5ab2942"
+  integrity sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
 
-"@babel/plugin-syntax-dynamic-import@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
-  integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
+"@babel/plugin-proposal-optional-chaining@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz#cce122203fc8a32794296fc377c6dedaf4363797"
+  integrity sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.0"
 
-"@babel/plugin-syntax-function-sent@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.2.0.tgz#91474d4d400604e4c6cbd4d77cd6cb3b8565576c"
-  integrity sha512-2MOVuJ6IMAifp2cf0RFkHQaOvHpbBYyWCvgtF/WVqXhTd7Bgtov8iXVCadLXp2FN1BrI2EFl+JXuwXy0qr3KoQ==
+"@babel/plugin-proposal-private-methods@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389"
+  integrity sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-create-class-features-plugin" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-json-strings@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470"
-  integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==
+"@babel/plugin-proposal-unicode-property-regex@^7.12.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz#2a183958d417765b9eae334f47758e5d6a82e072"
+  integrity sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-object-rest-spread@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
-  integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==
+"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4":
+  version "7.8.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+  integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-optional-catch-binding@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c"
-  integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==
+"@babel/plugin-syntax-class-properties@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978"
+  integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-top-level-await@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.0.tgz#f5699549f50bbe8d12b1843a4e82f0a37bb65f4d"
-  integrity sha512-hi8FUNiFIY1fnUI2n1ViB1DR0R4QeK4iHcTlW6aJkrPoTdb8Rf1EMQ6GT3f67DDkYyWgew9DFoOZ6gOoEsdzTA==
+"@babel/plugin-syntax-dynamic-import@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
+  integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-arrow-functions@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
-  integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+  integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.3"
 
-"@babel/plugin-transform-async-to-generator@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.0.tgz#e2b84f11952cf5913fe3438b7d2585042772f492"
-  integrity sha512-vLI2EFLVvRBL3d8roAMqtVY0Bm9C1QzLkdS57hiKrjUBSqsQYrBsMCeOg/0KK7B0eK9V71J5mWcha9yyoI2tZw==
+"@babel/plugin-syntax-function-sent@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.12.1.tgz#b5355304f0b0fb0cb1bf315903bf7cd13608e811"
+  integrity sha512-mtBQvNHcIzLnmQZhgzigzrgFzIe95WvBXJuTN0m4CvhDK0gRNQ2MC2AVSzB6w7VnVh/z5+0iHFcbfqKMlFwTkQ==
   dependencies:
-    "@babel/helper-module-imports" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-remap-async-to-generator" "^7.7.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-block-scoped-functions@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190"
-  integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==
+"@babel/plugin-syntax-json-strings@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+  integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-block-scoping@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d"
-  integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+  integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    lodash "^4.17.11"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-block-scoping@^7.6.3":
-  version "7.6.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a"
-  integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+  integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    lodash "^4.17.13"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-classes@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6"
-  integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+  integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-define-map" "^7.4.4"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.4.4"
-    "@babel/helper-split-export-declaration" "^7.4.4"
-    globals "^11.1.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-classes@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.0.tgz#b411ecc1b8822d24b81e5d184f24149136eddd4a"
-  integrity sha512-/b3cKIZwGeUesZheU9jNYcwrEA7f/Bo4IdPmvp7oHgvks2majB5BoT5byAql44fiNQYOPzhk2w8DbgfuafkMoA==
+"@babel/plugin-syntax-object-rest-spread@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+  integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.7.0"
-    "@babel/helper-define-map" "^7.7.0"
-    "@babel/helper-function-name" "^7.7.0"
-    "@babel/helper-optimise-call-expression" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.7.0"
-    "@babel/helper-split-export-declaration" "^7.7.0"
-    globals "^11.1.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-computed-properties@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da"
-  integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==
+"@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+  integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-destructuring@^7.6.0":
-  version "7.6.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6"
-  integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==
+"@babel/plugin-syntax-optional-chaining@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+  integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-transform-dotall-regex@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.0.tgz#c5c9ecacab3a5e0c11db6981610f0c32fd698b3b"
-  integrity sha512-3QQlF7hSBnSuM1hQ0pS3pmAbWLax/uGNCbPBND9y+oJ4Y776jsyujG2k0Sn2Aj2a0QwVOiOFL5QVPA7spjvzSA==
+"@babel/plugin-syntax-top-level-await@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0"
+  integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-duplicate-keys@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853"
-  integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==
+"@babel/plugin-transform-arrow-functions@^7.12.1", "@babel/plugin-transform-arrow-functions@^7.8.3":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3"
+  integrity sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-exponentiation-operator@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008"
-  integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==
+"@babel/plugin-transform-async-to-generator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1"
+  integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-module-imports" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.12.1"
 
-"@babel/plugin-transform-for-of@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
-  integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==
+"@babel/plugin-transform-block-scoped-functions@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz#f2a1a365bde2b7112e0a6ded9067fdd7c07905d9"
+  integrity sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-function-name@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.0.tgz#0fa786f1eef52e3b7d4fc02e54b2129de8a04c2a"
-  integrity sha512-P5HKu0d9+CzZxP5jcrWdpe7ZlFDe24bmqP6a6X8BHEBl/eizAsY8K6LX8LASZL0Jxdjm5eEfzp+FIrxCm/p8bA==
+"@babel/plugin-transform-block-scoping@^7.12.1", "@babel/plugin-transform-block-scoping@^7.8.3":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz#f0ee727874b42a208a48a586b84c3d222c2bbef1"
+  integrity sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==
   dependencies:
-    "@babel/helper-function-name" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1"
-  integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==
+"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.8.3":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz#65e650fcaddd3d88ddce67c0f834a3d436a32db6"
+  integrity sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-define-map" "^7.10.4"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.12.1"
+    "@babel/helper-split-export-declaration" "^7.10.4"
+    globals "^11.1.0"
 
-"@babel/plugin-transform-member-expression-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d"
-  integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==
+"@babel/plugin-transform-computed-properties@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz#d68cf6c9b7f838a8a4144badbe97541ea0904852"
+  integrity sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-amd@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91"
-  integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==
+"@babel/plugin-transform-destructuring@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz#b9a570fe0d0a8d460116413cb4f97e8e08b2f847"
+  integrity sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-commonjs@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.0.tgz#3e5ffb4fd8c947feede69cbe24c9554ab4113fe3"
-  integrity sha512-KEMyWNNWnjOom8vR/1+d+Ocz/mILZG/eyHHO06OuBQ2aNhxT62fr4y6fGOplRx+CxCSp3IFwesL8WdINfY/3kg==
+"@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.4.4":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz#a1d16c14862817b6409c0a678d6f9373ca9cd975"
+  integrity sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-simple-access" "^7.7.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-systemjs@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.0.tgz#9baf471213af9761c1617bb12fd278e629041417"
-  integrity sha512-ZAuFgYjJzDNv77AjXRqzQGlQl4HdUM6j296ee4fwKVZfhDR9LAGxfvXjBkb06gNETPnN0sLqRm9Gxg4wZH6dXg==
+"@babel/plugin-transform-duplicate-keys@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz#745661baba295ac06e686822797a69fbaa2ca228"
+  integrity sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-umd@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.0.tgz#d62c7da16670908e1d8c68ca0b5d4c0097b69966"
-  integrity sha512-u7eBA03zmUswQ9LQ7Qw0/ieC1pcAkbp5OQatbWUzY1PaBccvuJXUkYzoN1g7cqp7dbTu6Dp9bXyalBvD04AANA==
+"@babel/plugin-transform-exponentiation-operator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz#b0f2ed356ba1be1428ecaf128ff8a24f02830ae0"
+  integrity sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==
   dependencies:
-    "@babel/helper-module-transforms" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.0.tgz#358e6fd869b9a4d8f5cbc79e4ed4fc340e60dcaf"
-  integrity sha512-+SicSJoKouPctL+j1pqktRVCgy+xAch1hWWTMy13j0IflnyNjaoskj+DwRQFimHbLqO3sq2oN2CXMvXq3Bgapg==
+"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.8.4":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz#07640f28867ed16f9511c99c888291f560921cfa"
+  integrity sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.7.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-new-target@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5"
-  integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==
+"@babel/plugin-transform-function-name@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz#2ec76258c70fe08c6d7da154003a480620eba667"
+  integrity sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-object-super@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9"
-  integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==
+"@babel/plugin-transform-literals@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz#d73b803a26b37017ddf9d3bb8f4dc58bfb806f57"
+  integrity sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.5.5"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-parameters@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16"
-  integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==
+"@babel/plugin-transform-member-expression-literals@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz#496038602daf1514a64d43d8e17cbb2755e0c3ad"
+  integrity sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==
   dependencies:
-    "@babel/helper-call-delegate" "^7.4.4"
-    "@babel/helper-get-function-arity" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-property-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905"
-  integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==
+"@babel/plugin-transform-modules-amd@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz#3154300b026185666eebb0c0ed7f8415fefcf6f9"
+  integrity sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-regenerator@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.0.tgz#f1b20b535e7716b622c99e989259d7dd942dd9cc"
-  integrity sha512-AXmvnC+0wuj/cFkkS/HFHIojxH3ffSXE+ttulrqWjZZRaUOonfJc60e1wSNT4rV8tIunvu/R3wCp71/tLAa9xg==
+"@babel/plugin-transform-modules-commonjs@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz#fa403124542636c786cf9b460a0ffbb48a86e648"
+  integrity sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==
   dependencies:
-    regenerator-transform "^0.14.0"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-simple-access" "^7.12.1"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-reserved-words@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634"
-  integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==
+"@babel/plugin-transform-modules-systemjs@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz#663fea620d593c93f214a464cd399bf6dc683086"
+  integrity sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-hoist-variables" "^7.10.4"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-validator-identifier" "^7.10.4"
+    babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-shorthand-properties@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0"
-  integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==
+"@babel/plugin-transform-modules-umd@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz#eb5a218d6b1c68f3d6217b8fa2cc82fec6547902"
+  integrity sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-module-transforms" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-spread@^7.6.2":
-  version "7.6.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz#fc77cf798b24b10c46e1b51b1b88c2bf661bb8dd"
-  integrity sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz#b407f5c96be0d9f5f88467497fa82b30ac3e8753"
+  integrity sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.1"
 
-"@babel/plugin-transform-sticky-regex@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1"
-  integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==
+"@babel/plugin-transform-new-target@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz#80073f02ee1bb2d365c3416490e085c95759dec0"
+  integrity sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-template-literals@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0"
-  integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==
+"@babel/plugin-transform-object-super@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e"
+  integrity sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.12.1"
 
-"@babel/plugin-transform-typeof-symbol@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2"
-  integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==
+"@babel/plugin-transform-parameters@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz#d2e963b038771650c922eff593799c96d853255d"
+  integrity sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-unicode-regex@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.0.tgz#743d9bcc44080e3cc7d49259a066efa30f9187a3"
-  integrity sha512-RrThb0gdrNwFAqEAAx9OWgtx6ICK69x7i9tCnMdVrxQwSDp/Abu9DXFU5Hh16VP33Rmxh04+NGW28NsIkFvFKA==
+"@babel/plugin-transform-property-literals@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz#41bc81200d730abb4456ab8b3fbd5537b59adecd"
+  integrity sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/preset-env@^7.0.0":
-  version "7.7.1"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.1.tgz#04a2ff53552c5885cf1083e291c8dd5490f744bb"
-  integrity sha512-/93SWhi3PxcVTDpSqC+Dp4YxUu3qZ4m7I76k0w73wYfn7bGVuRIO4QUz95aJksbS+AD1/mT1Ie7rbkT0wSplaA==
+"@babel/plugin-transform-regenerator@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753"
+  integrity sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==
   dependencies:
-    "@babel/helper-module-imports" "^7.7.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-proposal-async-generator-functions" "^7.7.0"
-    "@babel/plugin-proposal-dynamic-import" "^7.7.0"
-    "@babel/plugin-proposal-json-strings" "^7.2.0"
-    "@babel/plugin-proposal-object-rest-spread" "^7.6.2"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
-    "@babel/plugin-proposal-unicode-property-regex" "^7.7.0"
-    "@babel/plugin-syntax-async-generators" "^7.2.0"
-    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
-    "@babel/plugin-syntax-json-strings" "^7.2.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-    "@babel/plugin-syntax-top-level-await" "^7.7.0"
-    "@babel/plugin-transform-arrow-functions" "^7.2.0"
-    "@babel/plugin-transform-async-to-generator" "^7.7.0"
-    "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
-    "@babel/plugin-transform-block-scoping" "^7.6.3"
-    "@babel/plugin-transform-classes" "^7.7.0"
-    "@babel/plugin-transform-computed-properties" "^7.2.0"
-    "@babel/plugin-transform-destructuring" "^7.6.0"
-    "@babel/plugin-transform-dotall-regex" "^7.7.0"
-    "@babel/plugin-transform-duplicate-keys" "^7.5.0"
-    "@babel/plugin-transform-exponentiation-operator" "^7.2.0"
-    "@babel/plugin-transform-for-of" "^7.4.4"
-    "@babel/plugin-transform-function-name" "^7.7.0"
-    "@babel/plugin-transform-literals" "^7.2.0"
-    "@babel/plugin-transform-member-expression-literals" "^7.2.0"
-    "@babel/plugin-transform-modules-amd" "^7.5.0"
-    "@babel/plugin-transform-modules-commonjs" "^7.7.0"
-    "@babel/plugin-transform-modules-systemjs" "^7.7.0"
-    "@babel/plugin-transform-modules-umd" "^7.7.0"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.0"
-    "@babel/plugin-transform-new-target" "^7.4.4"
-    "@babel/plugin-transform-object-super" "^7.5.5"
-    "@babel/plugin-transform-parameters" "^7.4.4"
-    "@babel/plugin-transform-property-literals" "^7.2.0"
-    "@babel/plugin-transform-regenerator" "^7.7.0"
-    "@babel/plugin-transform-reserved-words" "^7.2.0"
-    "@babel/plugin-transform-shorthand-properties" "^7.2.0"
-    "@babel/plugin-transform-spread" "^7.6.2"
-    "@babel/plugin-transform-sticky-regex" "^7.2.0"
-    "@babel/plugin-transform-template-literals" "^7.4.4"
-    "@babel/plugin-transform-typeof-symbol" "^7.2.0"
-    "@babel/plugin-transform-unicode-regex" "^7.7.0"
-    "@babel/types" "^7.7.1"
-    browserslist "^4.6.0"
-    core-js-compat "^3.1.1"
-    invariant "^2.2.2"
-    js-levenshtein "^1.1.3"
-    semver "^5.5.0"
+    regenerator-transform "^0.14.2"
 
-"@babel/runtime-corejs3@^7.10.2":
-  version "7.11.2"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz#02c3029743150188edeb66541195f54600278419"
-  integrity sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==
+"@babel/plugin-transform-reserved-words@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz#6fdfc8cc7edcc42b36a7c12188c6787c873adcd8"
+  integrity sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==
   dependencies:
-    core-js-pure "^3.0.0"
-    regenerator-runtime "^0.13.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2":
-  version "7.11.2"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
-  integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
+"@babel/plugin-transform-shorthand-properties@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3"
+  integrity sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==
   dependencies:
-    regenerator-runtime "^0.13.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/runtime@^7.4.4", "@babel/runtime@^7.5.1":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
-  integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
+"@babel/plugin-transform-spread@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz#527f9f311be4ec7fdc2b79bb89f7bf884b3e1e1e"
+  integrity sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==
   dependencies:
-    regenerator-runtime "^0.13.2"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
 
-"@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2":
-  version "7.7.7"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf"
-  integrity sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==
+"@babel/plugin-transform-sticky-regex@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz#5c24cf50de396d30e99afc8d1c700e8bce0f5caf"
+  integrity sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ==
   dependencies:
-    regenerator-runtime "^0.13.2"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
 
-"@babel/runtime@^7.7.2":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
-  integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
+"@babel/plugin-transform-template-literals@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz#b43ece6ed9a79c0c71119f576d299ef09d942843"
+  integrity sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==
   dependencies:
-    regenerator-runtime "^0.13.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/standalone@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.5.tgz#60ded549756cf749eb6db0a54c21b4c48c7e18e5"
-  integrity sha512-Ddjq6OS+NxpJdvMEMiKVU4BCg/2IOpbP+2JNM9ZY1HULxlZGTyNXKIqXHRqzV0N6e+51zmTwQg+c1Lfs44bBHg==
+"@babel/plugin-transform-typeof-symbol@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a"
+  integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/template@^7.1.0", "@babel/template@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237"
-  integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==
+"@babel/plugin-transform-unicode-escapes@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709"
+  integrity sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/parser" "^7.4.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-regex@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz#cc9661f61390db5c65e3febaccefd5c6ac3faecb"
+  integrity sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/preset-env@^7.0.0":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2"
+  integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==
+  dependencies:
+    "@babel/compat-data" "^7.12.1"
+    "@babel/helper-compilation-targets" "^7.12.1"
+    "@babel/helper-module-imports" "^7.12.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-validator-option" "^7.12.1"
+    "@babel/plugin-proposal-async-generator-functions" "^7.12.1"
+    "@babel/plugin-proposal-class-properties" "^7.12.1"
+    "@babel/plugin-proposal-dynamic-import" "^7.12.1"
+    "@babel/plugin-proposal-export-namespace-from" "^7.12.1"
+    "@babel/plugin-proposal-json-strings" "^7.12.1"
+    "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1"
+    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1"
+    "@babel/plugin-proposal-numeric-separator" "^7.12.1"
+    "@babel/plugin-proposal-object-rest-spread" "^7.12.1"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.12.1"
+    "@babel/plugin-proposal-optional-chaining" "^7.12.1"
+    "@babel/plugin-proposal-private-methods" "^7.12.1"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.12.1"
+    "@babel/plugin-syntax-async-generators" "^7.8.0"
+    "@babel/plugin-syntax-class-properties" "^7.12.1"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+    "@babel/plugin-syntax-json-strings" "^7.8.0"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+    "@babel/plugin-syntax-top-level-await" "^7.12.1"
+    "@babel/plugin-transform-arrow-functions" "^7.12.1"
+    "@babel/plugin-transform-async-to-generator" "^7.12.1"
+    "@babel/plugin-transform-block-scoped-functions" "^7.12.1"
+    "@babel/plugin-transform-block-scoping" "^7.12.1"
+    "@babel/plugin-transform-classes" "^7.12.1"
+    "@babel/plugin-transform-computed-properties" "^7.12.1"
+    "@babel/plugin-transform-destructuring" "^7.12.1"
+    "@babel/plugin-transform-dotall-regex" "^7.12.1"
+    "@babel/plugin-transform-duplicate-keys" "^7.12.1"
+    "@babel/plugin-transform-exponentiation-operator" "^7.12.1"
+    "@babel/plugin-transform-for-of" "^7.12.1"
+    "@babel/plugin-transform-function-name" "^7.12.1"
+    "@babel/plugin-transform-literals" "^7.12.1"
+    "@babel/plugin-transform-member-expression-literals" "^7.12.1"
+    "@babel/plugin-transform-modules-amd" "^7.12.1"
+    "@babel/plugin-transform-modules-commonjs" "^7.12.1"
+    "@babel/plugin-transform-modules-systemjs" "^7.12.1"
+    "@babel/plugin-transform-modules-umd" "^7.12.1"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1"
+    "@babel/plugin-transform-new-target" "^7.12.1"
+    "@babel/plugin-transform-object-super" "^7.12.1"
+    "@babel/plugin-transform-parameters" "^7.12.1"
+    "@babel/plugin-transform-property-literals" "^7.12.1"
+    "@babel/plugin-transform-regenerator" "^7.12.1"
+    "@babel/plugin-transform-reserved-words" "^7.12.1"
+    "@babel/plugin-transform-shorthand-properties" "^7.12.1"
+    "@babel/plugin-transform-spread" "^7.12.1"
+    "@babel/plugin-transform-sticky-regex" "^7.12.1"
+    "@babel/plugin-transform-template-literals" "^7.12.1"
+    "@babel/plugin-transform-typeof-symbol" "^7.12.1"
+    "@babel/plugin-transform-unicode-escapes" "^7.12.1"
+    "@babel/plugin-transform-unicode-regex" "^7.12.1"
+    "@babel/preset-modules" "^0.1.3"
+    "@babel/types" "^7.12.1"
+    core-js-compat "^3.6.2"
+    semver "^5.5.0"
+
+"@babel/preset-modules@^0.1.3":
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
+  integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+    "@babel/plugin-transform-dotall-regex" "^7.4.4"
     "@babel/types" "^7.4.4"
+    esutils "^2.0.2"
 
-"@babel/template@^7.7.0":
-  version "7.7.0"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0"
-  integrity sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==
+"@babel/runtime-corejs3@^7.10.2":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz#51b9092befbeeed938335a109dbe0df51451e9dc"
+  integrity sha512-umhPIcMrlBZ2aTWlWjUseW9LjQKxi1dpFlQS8DzsxB//5K+u6GLTC/JliPKHsd5kJVPIU6X/Hy0YvWOYPcMxBw==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/parser" "^7.7.0"
-    "@babel/types" "^7.7.0"
+    core-js-pure "^3.0.0"
+    regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.1", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740"
+  integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
+"@babel/standalone@^7.4.5":
+  version "7.12.3"
+  resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.12.3.tgz#65f37d670ebb4083e74fd9b5178044ccc64d4f83"
+  integrity sha512-+zZdF3o/CEeSQ+WDZWeqdVVHGc1oQ+EZuYOQnCGyngNXroH+uMhHa00ki6egI/EddI6vHMH3TEdQhXAx98usXg==
 
-"@babel/template@^7.8.6":
+"@babel/template@^7.10.4", "@babel/template@^7.8.6":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
   integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
@@ -890,63 +829,30 @@
     "@babel/parser" "^7.10.4"
     "@babel/types" "^7.10.4"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
-  integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
+"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e"
+  integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.4.4"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.4.4"
-    "@babel/parser" "^7.4.5"
-    "@babel/types" "^7.4.4"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.11"
-
-"@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
-  version "7.7.2"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.2.tgz#ef0a65e07a2f3c550967366b3d9b62a2dcbeae09"
-  integrity sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==
-  dependencies:
-    "@babel/code-frame" "^7.5.5"
-    "@babel/generator" "^7.7.2"
-    "@babel/helper-function-name" "^7.7.0"
-    "@babel/helper-split-export-declaration" "^7.7.0"
-    "@babel/parser" "^7.7.2"
-    "@babel/types" "^7.7.2"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.12.1"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.11.0"
+    "@babel/parser" "^7.12.1"
+    "@babel/types" "^7.12.1"
     debug "^4.1.0"
     globals "^11.1.0"
-    lodash "^4.17.13"
+    lodash "^4.17.19"
 
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
-  integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
-  dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.11"
-    to-fast-properties "^2.0.0"
-
-"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.9.5":
-  version "7.10.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15"
-  integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==
+"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.9.5":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae"
+  integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==
   dependencies:
     "@babel/helper-validator-identifier" "^7.10.4"
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.7.0", "@babel/types@^7.7.1", "@babel/types@^7.7.2":
-  version "7.7.2"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.2.tgz#550b82e5571dcd174af576e23f0adba7ffc683f7"
-  integrity sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==
-  dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.13"
-    to-fast-properties "^2.0.0"
-
 "@cypress/listr-verbose-renderer@0.4.1":
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a"
@@ -958,12 +864,12 @@
     figures "^1.7.0"
 
 "@cypress/webpack-preprocessor@^4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.0.tgz#8c4debc0b1abf045b62524d1996dd9aeaf7e86a8"
-  integrity sha512-LbxsdYVpHGoC2fMOdW0aQvuvVRD7aZx8p8DrP53HISpl7bD1PqLGWKzhHn7cGG24UHycBJrbaEeKEosW29W1dg==
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.5.tgz#b47d515d2540af977ee8b69d7c4eed64e3027668"
+  integrity sha512-B4miSaS3VCMVSlfuvbWCjytTywdnquRsF1tQ3quC7TGUzEXnQZ4+o8WUKibjMozrOomALkUdMxqOJ1ib5oFkKw==
   dependencies:
-    bluebird "3.5.0"
-    debug "3.1.0"
+    bluebird "3.7.1"
+    debug "4.1.1"
   optionalDependencies:
     "@babel/core" "^7.0.1"
     "@babel/preset-env" "^7.0.0"
@@ -982,15 +888,6 @@
   resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
   integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
 
-"@jest/types@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
-  integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    "@types/istanbul-reports" "^1.1.1"
-    "@types/yargs" "^12.0.9"
-
 "@jest/types@^24.9.0":
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59"
@@ -1000,10 +897,20 @@
     "@types/istanbul-reports" "^1.1.1"
     "@types/yargs" "^13.0.0"
 
-"@jest/types@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71"
-  integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==
+"@jest/types@^25.5.0":
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
+  integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^1.1.1"
+    "@types/yargs" "^15.0.0"
+    chalk "^3.0.0"
+
+"@jest/types@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.5.2.tgz#44c24f30c8ee6c7f492ead9ec3f3c62a5289756d"
+  integrity sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^3.0.0"
@@ -1012,14 +919,14 @@
     chalk "^4.0.0"
 
 "@sheerun/mutationobserver-shim@^0.3.2":
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b"
-  integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25"
+  integrity sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw==
 
 "@slack/client@^3.5.4":
-  version "3.15.0"
-  resolved "https://registry.yarnpkg.com/@slack/client/-/client-3.15.0.tgz#796ee2b1182cd37fadbaeb37752121b2028a1704"
-  integrity sha512-MIgf5s9PrcxFaPlkJ2cFOhrfh9/KOmUKK5GG/Eka1IJK7+oBCscJFnQ6FfYnZICwIQxWkkuiXmeWYWNevZhCLg==
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/@slack/client/-/client-3.16.0.tgz#a9963a6a24d41e74dc0f7ff09d9f1b2a01b97bfd"
+  integrity sha512-CWr7a3rTVrN5Vs8GYReRAvTourbXHOqB1zglcskj05ICH4GZL5BOAza2ARai+qc3Nz0nY08Bozi1x0014KOqlg==
   dependencies:
     async "^1.5.0"
     bluebird "^3.3.3"
@@ -1035,30 +942,31 @@
     ws "^1.0.1"
 
 "@testing-library/cypress@^5.0.2":
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-5.0.2.tgz#68746fc9db11dabcc4bf883e25b64316f637be71"
-  integrity sha512-AmvBLE+isA/vpdBTXpJ5tu+UYMDgqNW015RVa0nBPJHrv5UXNlmDZyu8tpkxGTFhcf+iFSA2pHuK1jUCQ8PnXQ==
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-5.3.1.tgz#96c9bd0f72eb2330b4b5154dd71e81d2c644ce62"
+  integrity sha512-MMg1LM+bbz4CUIZpE3LXODBUoDOp6H4DinezI/DJ357UNS1W6zZohxAul5Nol23NKclB/GQOs/4cuge/smt9Gg==
   dependencies:
-    "@babel/runtime" "^7.5.5"
-    "@testing-library/dom" "^6.0.0"
-    "@types/testing-library__cypress" "^5.0.0"
+    "@babel/runtime" "^7.8.4"
+    "@testing-library/dom" "^6.15.0"
+    "@types/testing-library__cypress" "^5.0.3"
 
-"@testing-library/dom@^6.0.0":
-  version "6.11.0"
-  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.11.0.tgz#962a38f1a721fdb7c9e35e7579e33ff13a00eda4"
-  integrity sha512-Pkx9LMIGshyNbfmecjt18rrAp/ayMqGH674jYER0SXj0iG9xZc+zWRjk2Pg9JgPBDvwI//xGrI/oOQkAi4YEew==
+"@testing-library/dom@^6.15.0":
+  version "6.16.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.16.0.tgz#04ada27ed74ad4c0f0d984a1245bb29b1fd90ba9"
+  integrity sha512-lBD88ssxqEfz0wFL6MeUyyWZfV/2cjEZZV3YRpb2IoJRej/4f1jB0TzqIOznTpfR1r34CNesrubxwIlAQ8zgPA==
   dependencies:
-    "@babel/runtime" "^7.6.2"
+    "@babel/runtime" "^7.8.4"
     "@sheerun/mutationobserver-shim" "^0.3.2"
-    "@types/testing-library__dom" "^6.0.0"
-    aria-query "3.0.0"
-    pretty-format "^24.9.0"
-    wait-for-expect "^3.0.0"
+    "@types/testing-library__dom" "^6.12.1"
+    aria-query "^4.0.2"
+    dom-accessibility-api "^0.3.0"
+    pretty-format "^25.1.0"
+    wait-for-expect "^3.0.2"
 
-"@testing-library/dom@^7.23.0":
-  version "7.24.1"
-  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.1.tgz#0e8acd042070f2c1b183fbfe5c0d38b3194ad3c0"
-  integrity sha512-TemHWY59gvzcScGiE5eooZpzYk9GaED0TuuK4WefbIc/DQg0L5wOpnj7MIEeAGF3B7Ekf1kvmVnQ97vwz4Lmhg==
+"@testing-library/dom@^7.11.0", "@testing-library/dom@^7.26.0":
+  version "7.26.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.0.tgz#da4d052dc426a4ccc916303369c6e7552126f680"
+  integrity sha512-fyKFrBbS1IigaE3FV21LyeC7kSGF84lqTlSYdKmGaHuK2eYQ/bXVPM5vAa2wx/AU1iPD6oQHsxy2QQ17q9AMCg==
   dependencies:
     "@babel/code-frame" "^7.10.4"
     "@babel/runtime" "^7.10.3"
@@ -1066,12 +974,13 @@
     aria-query "^4.2.2"
     chalk "^4.1.0"
     dom-accessibility-api "^0.5.1"
+    lz-string "^1.4.4"
     pretty-format "^26.4.2"
 
 "@testing-library/jest-dom@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.0.0.tgz#56eee8dd183fe14a682fda7aca6413ec4e5303d2"
-  integrity sha512-YQA/LnRRfqHV5YRauawOGgMDgq43XfyqCz3whmxIPyrfvTdjLCNyY/BseGaa48y54yb3oiRo/NZT0oXNMQdkTA==
+  version "4.2.4"
+  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742"
+  integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==
   dependencies:
     "@babel/runtime" "^7.5.1"
     chalk "^2.4.1"
@@ -1084,44 +993,39 @@
     redent "^3.0.0"
 
 "@testing-library/react@^11.0.2":
-  version "11.0.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.2.tgz#4588cc537085907bd1d98b531eb247dbbf57b1cc"
-  integrity sha512-iOuNNHt4ZgMH5trSKC4kaWDcKzUOf7e7KQIcu7xvGCd68/w1EegbW89F9T5sZ4IjS0gAXdvOfZbG9ESZ7YjOug==
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.1.0.tgz#dfb4b3177d05a8ccf156b5fd14a5550e91d7ebe4"
+  integrity sha512-Nfz58jGzW0tgg3irmTB7sa02JLkLnCk+QN3XG6WiaGQYb0Qc4Ok00aujgjdxlIQWZHbb4Zj5ZOIeE9yKFSs4sA==
   dependencies:
     "@babel/runtime" "^7.11.2"
-    "@testing-library/dom" "^7.23.0"
+    "@testing-library/dom" "^7.26.0"
 
 "@types/aria-query@^4.2.0":
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
   integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
 
-"@types/color-name@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
-  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
-
 "@types/invariant@^2.2.29":
-  version "2.2.29"
-  resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.29.tgz#aa845204cd0a289f65d47e0de63a6a815e30cc66"
-  integrity sha512-lRVw09gOvgviOfeUrKc/pmTiRZ7g7oDOU6OAutyuSHpm1/o2RaBQvRhgK8QEdu+FFuw/wnWb29A/iuxv9i8OpQ==
+  version "2.2.34"
+  resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe"
+  integrity sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg==
 
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
-  integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
+  integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
 
 "@types/istanbul-lib-report@*":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c"
-  integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+  integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
   dependencies:
     "@types/istanbul-lib-coverage" "*"
 
 "@types/istanbul-reports@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
-  integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2"
+  integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==
   dependencies:
     "@types/istanbul-lib-coverage" "*"
     "@types/istanbul-lib-report" "*"
@@ -1133,25 +1037,30 @@
   dependencies:
     "@types/istanbul-lib-report" "*"
 
+"@types/json-schema@^7.0.5":
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
+  integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
+
 "@types/json5@^0.0.29":
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
 "@types/lodash@^4.14.107":
-  version "4.14.109"
-  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.109.tgz#b1c4442239730bf35cabaf493c772b18c045886d"
-  integrity sha512-hop8SdPUEzbcJm6aTsmuwjIYQo1tqLseKCM+s2bBqTU2gErwI4fE+aqUVOlscPSQbKHKgtMMPoC+h4AIGOJYvw==
+  version "4.14.162"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.162.tgz#65d78c397e0d883f44afbf1f7ba9867022411470"
+  integrity sha512-alvcho1kRUnnD1Gcl4J+hK0eencvzq9rmzvFPRmP5rPHx9VVsJj6bKLTATPVf9ktgv4ujzh7T+XWKp+jhuODig==
 
 "@types/node@*":
-  version "10.12.21"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e"
-  integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==
+  version "14.11.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef"
+  integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA==
 
 "@types/node@^8.10.11":
-  version "8.10.19"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.19.tgz#66b5b6325c048cbf4512b7a88b0e79c2ee99d3d2"
-  integrity sha512-+PU57o6DtOSx0/algmxgCwWrmCiomwC/K+LPfXonT0tQMbNTjHEqVzwL9dFEhFoPmLFIiSWjRorLH6Z0hJMT+Q==
+  version "8.10.65"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.65.tgz#d2b5d0eb97e28cc1e28008d2872e4da8638a8ea3"
+  integrity sha512-xdcqtQl1g3p/49kmcj7ZixPWOcNHA1tYNz+uN0PJEcgtN6zywK74aacTnd3eFGPuBpD7kK8vowmMRkUt6jHU/Q==
 
 "@types/parse-json@^4.0.0":
   version "4.0.0"
@@ -1170,49 +1079,44 @@
   resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
   integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
 
-"@types/testing-library__cypress@^5.0.0":
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/@types/testing-library__cypress/-/testing-library__cypress-5.0.1.tgz#d05a264eb5d5a45659699a4e0309a7cebe57b344"
-  integrity sha512-z2JKxVTh3PDeLbATRofwDUImEwuytsBeu8Si6YLzxwc2HK4xGIDs50HFlwYD9MPlclGDQRm0QL7ynnaIjRtCGg==
+"@types/testing-library__cypress@^5.0.3":
+  version "5.0.8"
+  resolved "https://registry.yarnpkg.com/@types/testing-library__cypress/-/testing-library__cypress-5.0.8.tgz#6a8effce8218c5be42c1775b26dcbdcc273fda62"
+  integrity sha512-1dTaoaCR6PWVRKXZAHcWvnAzHpTDKUKWw1pq4yzyoyI/RQ4KRySA9EFLwS0uEugTIPPgBaPUnXK5OwTaQoQp7g==
   dependencies:
-    "@types/testing-library__dom" "*"
-    cypress "^3.5.0"
+    "@testing-library/dom" "^7.11.0"
+    cypress "*"
 
-"@types/testing-library__dom@*", "@types/testing-library__dom@^6.0.0":
-  version "6.11.1"
-  resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.11.1.tgz#6058a6ac391db679f7c60dbb27b81f0620de2dd9"
-  integrity sha512-ImChHtQqmjwraRLqBC2sgSQFtczeFvBmBcfhTYZn/3KwXbyD07LQykEQ0xJo7QHc1GbVvf7pRyGaIe6PkCdxEw==
+"@types/testing-library__dom@^6.12.1":
+  version "6.14.0"
+  resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.14.0.tgz#1aede831cb4ed4a398448df5a2c54b54a365644e"
+  integrity sha512-sMl7OSv0AvMOqn1UJ6j1unPMIHRXen0Ita1ujnMX912rrOcawe4f7wu0Zt9GIQhBhJvH2BaibqFgQ3lP+Pj2hA==
   dependencies:
     pretty-format "^24.3.0"
 
 "@types/yargs-parser@*":
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
-  integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==
-
-"@types/yargs@^12.0.9":
-  version "12.0.12"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
-  integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
+  version "15.0.0"
+  resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
+  integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
 
 "@types/yargs@^13.0.0":
-  version "13.0.5"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.5.tgz#18121bfd39dc12f280cee58f92c5b21d32041908"
-  integrity sha512-CF/+sxTO7FOwbIRL4wMv0ZYLCRfMid2HQpzDRyViH7kSpfoAFiMdGqKIxb1PxWfjtQXQhnQuD33lvRHNwr809Q==
+  version "13.0.11"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.11.tgz#def2f0c93e4bdf2c61d7e34899b17e34be28d3b1"
+  integrity sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ==
   dependencies:
     "@types/yargs-parser" "*"
 
 "@types/yargs@^15.0.0":
-  version "15.0.5"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
-  integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
+  version "15.0.9"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.9.tgz#524cd7998fe810cdb02f26101b699cccd156ff19"
+  integrity sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==
   dependencies:
     "@types/yargs-parser" "*"
 
 JSONStream@^1.0.3:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea"
-  integrity sha1-wQI3G27Dp887hHygDCC7D85Mbeo=
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
+  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
   dependencies:
     jsonparse "^1.2.0"
     through ">=2.2.7 <3"
@@ -1222,23 +1126,18 @@ abab@^1.0.3:
   resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
   integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=
 
-abbrev@1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-
-accepts@~1.3.4:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
-  integrity sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=
+accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
+  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
   dependencies:
-    mime-types "~2.1.16"
-    negotiator "0.6.1"
+    mime-types "~2.1.24"
+    negotiator "0.6.2"
 
 ace-builds@^1.4.7:
-  version "1.4.7"
-  resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.7.tgz#56e5465270b6c48a48d30e70d6b8f6b92fbf2b08"
-  integrity sha512-gwQGVFewBopRLho08BfahyvRa9FlB43JUig5ItAKTYc9kJJsbA9QNz75p28QtQomoPQ9rJx82ymL21x4ZSZmdg==
+  version "1.4.12"
+  resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7"
+  integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==
 
 acorn-dynamic-import@^2.0.0:
   version "2.0.2"
@@ -1271,15 +1170,15 @@ acorn@^4.0.3, acorn@^4.0.4:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
   integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
 
-acorn@^5.0.0, acorn@^5.2.1:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
-  integrity sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==
+acorn@^5.0.0, acorn@^5.2.1, acorn@^5.5.0:
+  version "5.7.4"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
+  integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
 
 adm-zip@~0.4.3:
-  version "0.4.7"
-  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1"
-  integrity sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=
+  version "0.4.16"
+  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365"
+  integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==
 
 agent-base@2:
   version "2.1.1"
@@ -1289,25 +1188,49 @@ agent-base@2:
     extend "~3.0.0"
     semver "~5.0.1"
 
+agent-base@6:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
+  integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==
+  dependencies:
+    debug "4"
+
+airbnb-prop-types@^2.16.0:
+  version "2.16.0"
+  resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2"
+  integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==
+  dependencies:
+    array.prototype.find "^2.1.1"
+    function.prototype.name "^1.1.2"
+    is-regex "^1.1.0"
+    object-is "^1.1.2"
+    object.assign "^4.1.0"
+    object.entries "^1.1.2"
+    prop-types "^15.7.2"
+    prop-types-exact "^1.2.0"
+    react-is "^16.13.1"
+
 ajv-keywords@^1.0.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
   integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw=
 
-ajv-keywords@^2.0.0, ajv-keywords@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
-  integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=
+ajv-keywords@^3.1.0, ajv-keywords@^3.5.2:
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+  integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
-ajv@4.9.0:
-  version "4.9.0"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.9.0.tgz#5a358085747b134eb567d6d15e015f1d7802f45c"
-  integrity sha1-WjWAhXR7E061Z9bRXgFfHXgC9Fw=
+ajv@6.12.3:
+  version "6.12.3"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
+  integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
   dependencies:
-    co "^4.6.0"
-    json-stable-stringify "^1.0.1"
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
 
-ajv@^4.7.0, ajv@^4.9.1:
+ajv@^4.7.0:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
   integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=
@@ -1315,7 +1238,7 @@ ajv@^4.7.0, ajv@^4.9.1:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5:
+ajv@^5.0.0:
   version "5.5.2"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
   integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
@@ -1325,10 +1248,10 @@ ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5:
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.3.0"
 
-ajv@^6.12.3:
-  version "6.12.5"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
-  integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
+ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.4:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
@@ -1364,11 +1287,6 @@ ansi-html@0.0.7, ansi-html@^0.0.7:
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
   integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
 
-ansi-regex@^0.2.0, ansi-regex@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9"
-  integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=
-
 ansi-regex@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
@@ -1396,11 +1314,6 @@ ansi-style-parser@^1.0.1:
   dependencies:
     ansi-regex "^2.0.0"
 
-ansi-styles@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de"
-  integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=
-
 ansi-styles@^2.0.1, ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1414,11 +1327,10 @@ ansi-styles@^3.0.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1:
     color-convert "^1.9.0"
 
 ansi-styles@^4.0.0, ansi-styles@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
-  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
   dependencies:
-    "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
 ansi-styles@~1.0.0:
@@ -1452,10 +1364,18 @@ anymatch@^2.0.0:
     micromatch "^3.1.4"
     normalize-path "^2.1.1"
 
+anymatch@~3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
+  integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
 app-root-path@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46"
-  integrity sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
+  integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
 
 append-transform@^0.4.0:
   version "0.4.0"
@@ -1464,7 +1384,7 @@ append-transform@^0.4.0:
   dependencies:
     default-require-extensions "^1.0.0"
 
-aproba@^1.0.3, aproba@^1.1.1:
+aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
   integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
@@ -1474,30 +1394,14 @@ arch@2.1.1:
   resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
   integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==
 
-are-we-there-yet@~1.1.2:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
-  integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=
-  dependencies:
-    delegates "^1.0.0"
-    readable-stream "^2.0.6"
-
 argparse@^1.0.7:
-  version "1.0.9"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
-  integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+  integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
   dependencies:
     sprintf-js "~1.0.2"
 
-aria-query@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
-  integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=
-  dependencies:
-    ast-types-flow "0.0.7"
-    commander "^2.11.0"
-
-aria-query@^4.2.2:
+aria-query@^4.0.2, aria-query@^4.2.2:
   version "4.2.2"
   resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
   integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
@@ -1532,6 +1436,11 @@ array-equal@^1.0.0:
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
   integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
 
+array-filter@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
+  integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
+
 array-find-index@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -1548,19 +1457,11 @@ array-flatten@1.1.1:
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
 array-flatten@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
-  integrity sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=
-
-array-includes@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
-  integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.7.0"
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
+  integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
 
-array-includes@^3.1.1:
+array-includes@^3.0.3, array-includes@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
   integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
@@ -1569,11 +1470,6 @@ array-includes@^3.1.1:
     es-abstract "^1.17.0"
     is-string "^1.0.5"
 
-array-iterate@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.1.tgz#865bf7f8af39d6b0982c60902914ac76bc0108f6"
-  integrity sha1-hlv3+K851rCYLGCQKRSsdrwBCPY=
-
 array-parallel@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d"
@@ -1606,22 +1502,13 @@ array-unique@^0.3.2:
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
-array.prototype.find@^2.0.1:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
-  integrity sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.7.0"
-
-array.prototype.flat@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4"
-  integrity sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==
+array.prototype.find@^2.0.1, array.prototype.find@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c"
+  integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==
   dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.10.0"
-    function-bind "^1.1.1"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.4"
 
 array.prototype.flat@^1.2.3:
   version "1.2.3"
@@ -1631,7 +1518,7 @@ array.prototype.flat@^1.2.3:
     define-properties "^1.1.3"
     es-abstract "^1.17.0-next.1"
 
-arrify@^1.0.0, arrify@^1.0.1:
+arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
@@ -1641,19 +1528,22 @@ asap@^2.0.6, asap@~2.0.3:
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
 
-asn1.js@^4.0.0:
-  version "4.9.2"
-  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.2.tgz#8117ef4f7ed87cd8f89044b5bff97ac243a16c9a"
-  integrity sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==
+asn1.js@^5.2.0:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
+  integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
   dependencies:
     bn.js "^4.0.0"
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
+    safer-buffer "^2.1.0"
 
 asn1@~0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
-  integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
+  integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
+  dependencies:
+    safer-buffer "~2.1.0"
 
 assert-plus@1.0.0, assert-plus@^1.0.0:
   version "1.0.0"
@@ -1666,10 +1556,11 @@ assert-plus@^0.2.0:
   integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ=
 
 assert@^1.1.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
-  integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
+  integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
   dependencies:
+    object-assign "^4.1.1"
     util "0.10.3"
 
 assign-symbols@^1.0.0:
@@ -1677,30 +1568,25 @@ assign-symbols@^1.0.0:
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
-ast-types-flow@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
-  integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
-
 ast-types@0.11.3:
   version "0.11.3"
   resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8"
   integrity sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==
 
-ast-types@0.12.4:
-  version "0.12.4"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.4.tgz#71ce6383800f24efc9a1a3308f3a6e420a0974d1"
-  integrity sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw==
+ast-types@0.13.3:
+  version "0.13.3"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.3.tgz#50da3f28d17bdbc7969a3a2d83a0e4a72ae755a7"
+  integrity sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==
 
 ast-types@0.9.6:
   version "0.9.6"
   resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
   integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=
 
-async-each@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-  integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
+async-each@^1.0.0, async-each@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
+  integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
 async@2.6.1:
   version "2.6.1"
@@ -1709,17 +1595,17 @@ async@2.6.1:
   dependencies:
     lodash "^4.17.10"
 
-async@^1.4.0, async@^1.5.0, async@^1.5.2:
+async@^1.5.0:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
 
-async@^2.1.2, async@^2.1.4, async@^2.4.1:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
-  integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
+async@^2.1.2, async@^2.1.4, async@^2.4.1, async@^2.6.2:
+  version "2.6.3"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
+  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
   dependencies:
-    lodash "^4.14.0"
+    lodash "^4.17.14"
 
 async@~0.2.9:
   version "0.2.10"
@@ -1736,15 +1622,15 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
 
-atob@^2.1.1:
+atob@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
 autobind-decorator@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.1.0.tgz#4451240dbfeff46361c506575a63ed40f0e5bc68"
-  integrity sha512-bgyxeRi1R2Q8kWpHsb1c+lXCulbIAHsyZRddaS+agAUX3hFUVZMociwvRgeZi1zWvfqEEjybSv4zxWvFV8ydQQ==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c"
+  integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==
 
 autoprefixer@^6.0.2, autoprefixer@^6.3.1:
   version "6.7.7"
@@ -1768,15 +1654,10 @@ aws-sign2@~0.7.0:
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
-aws4@^1.2.1, aws4@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
-  integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
-
-aws4@^1.8.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
-  integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+aws4@^1.2.1, aws4@^1.8.0:
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
+  integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
 
 babel-cli@^6.26.0:
   version "6.26.0"
@@ -1809,32 +1690,7 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
     esutils "^2.0.2"
     js-tokens "^3.0.2"
 
-babel-core@^6.0.0, babel-core@^6.0.14, babel-core@^6.17.0, babel-core@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
-  integrity sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=
-  dependencies:
-    babel-code-frame "^6.26.0"
-    babel-generator "^6.26.0"
-    babel-helpers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-register "^6.26.0"
-    babel-runtime "^6.26.0"
-    babel-template "^6.26.0"
-    babel-traverse "^6.26.0"
-    babel-types "^6.26.0"
-    babylon "^6.18.0"
-    convert-source-map "^1.5.0"
-    debug "^2.6.8"
-    json5 "^0.5.1"
-    lodash "^4.17.4"
-    minimatch "^3.0.4"
-    path-is-absolute "^1.0.1"
-    private "^0.1.7"
-    slash "^1.0.0"
-    source-map "^0.5.6"
-
-babel-core@^6.26.3:
+babel-core@^6.0.0, babel-core@^6.0.14, babel-core@^6.17.0, babel-core@^6.26.0, babel-core@^6.26.3:
   version "6.26.3"
   resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
   integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
@@ -1884,9 +1740,9 @@ babel-generator@6.25.0:
     trim-right "^1.0.1"
 
 babel-generator@^6.18.0, babel-generator@^6.26.0:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
-  integrity sha1-rBriAHC3n248odMmlhMFN3TyDcU=
+  version "6.26.1"
+  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
+  integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
   dependencies:
     babel-messages "^6.23.0"
     babel-runtime "^6.26.0"
@@ -1894,7 +1750,7 @@ babel-generator@^6.18.0, babel-generator@^6.26.0:
     detect-indent "^4.0.0"
     jsesc "^1.3.0"
     lodash "^4.17.4"
-    source-map "^0.5.6"
+    source-map "^0.5.7"
     trim-right "^1.0.1"
 
 babel-helper-bindify-decorators@^6.24.1:
@@ -2057,14 +1913,15 @@ babel-loader@^7.1.5:
     mkdirp "^0.5.1"
 
 babel-loader@^8.0.2:
-  version "8.0.6"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
-  integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3"
+  integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==
   dependencies:
-    find-cache-dir "^2.0.0"
-    loader-utils "^1.0.2"
-    mkdirp "^0.5.1"
+    find-cache-dir "^2.1.0"
+    loader-utils "^1.4.0"
+    mkdirp "^0.5.3"
     pify "^4.0.1"
+    schema-utils "^2.6.5"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -2085,21 +1942,22 @@ babel-plugin-check-es2015-constants@^6.22.0:
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-dynamic-import-node@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
-  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
+babel-plugin-dynamic-import-node@^2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+  integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
   dependencies:
     object.assign "^4.1.0"
 
 babel-plugin-istanbul@^4.0.0:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e"
-  integrity sha1-Z2DN2Xf0EdPhdbsGTyvDJ9mbK24=
+  version "4.1.6"
+  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
+  integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==
   dependencies:
+    babel-plugin-syntax-object-rest-spread "^6.13.0"
     find-up "^2.1.0"
-    istanbul-lib-instrument "^1.7.5"
-    test-exclude "^4.1.1"
+    istanbul-lib-instrument "^1.10.1"
+    test-exclude "^4.2.1"
 
 babel-plugin-jest-hoist@^19.0.0:
   version "19.0.0"
@@ -2116,9 +1974,9 @@ babel-plugin-macros@^2.8.0:
     resolve "^1.12.0"
 
 babel-plugin-styled-components@^1.10.7:
-  version "1.10.7"
-  resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz#3494e77914e9989b33cc2d7b3b29527a949d635c"
-  integrity sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg==
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.11.1.tgz#5296a9e557d736c3186be079fff27c6665d63d76"
+  integrity sha512-YwrInHyKUk1PU3avIRdiLyCpM++18Rs1NgyMXEAQC33rIXs/vro0A+stf4sT0Gf22Got+xRWB8Cm0tw+qkRzBA==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.0.0"
     "@babel/helper-module-imports" "^7.0.0"
@@ -2185,7 +2043,7 @@ babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-s
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
   integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
 
-babel-plugin-syntax-object-rest-spread@^6.8.0:
+babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
   integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
@@ -2247,16 +2105,7 @@ babel-plugin-transform-class-properties@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-decorators-legacy@^1.3.4:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz#741b58f6c5bce9e6027e0882d9c994f04f366925"
-  integrity sha1-dBtY9sW86eYCfgiC2cmU8E82aSU=
-  dependencies:
-    babel-plugin-syntax-decorators "^6.1.18"
-    babel-runtime "^6.2.0"
-    babel-template "^6.3.0"
-
-babel-plugin-transform-decorators-legacy@^1.3.5:
+babel-plugin-transform-decorators-legacy@^1.3.4, babel-plugin-transform-decorators-legacy@^1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.5.tgz#0e492dffa0edd70529072887f8aa86d4dd8b40a1"
   integrity sha512-jYHwjzRXRelYQ1uGm353zNzf3QmtdCfvJbuYTZ4gKveK7M9H1fs3a5AKdY1JUDl0z97E30ukORW1dzhWvsabtA==
@@ -2380,9 +2229,9 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1:
     babel-template "^6.24.1"
 
 babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
-  version "6.26.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
-  integrity sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=
+  version "6.26.2"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
+  integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==
   dependencies:
     babel-plugin-transform-strict-mode "^6.24.1"
     babel-runtime "^6.26.0"
@@ -2563,14 +2412,14 @@ babel-plugin-transform-strict-mode@^6.24.1:
     babel-types "^6.24.1"
 
 babel-plugin-ttag@^1.7.26:
-  version "1.7.26"
-  resolved "https://registry.yarnpkg.com/babel-plugin-ttag/-/babel-plugin-ttag-1.7.26.tgz#7a99bbcc913e20fd2e67a0b70a2923fdd340f963"
-  integrity sha512-mzBqs972cQ1yiHf+aLS6QX2Z6jIFcFR5xd4S2gNpPsLzzWW8CoY/0wXHo9mO3mefNGliTCJLScQoAEBj1MwpWA==
+  version "1.7.27"
+  resolved "https://registry.yarnpkg.com/babel-plugin-ttag/-/babel-plugin-ttag-1.7.27.tgz#2764a9357bdfb073f7476f52b1194fe14f27e0a1"
+  integrity sha512-yCXaDZ+e5MP/4z5SazRU+koL8YTXTG1Uo3MkrHrvFJKEeiya0R3H5Ec4o+bPLbLs8UY1H/ZSO2uRcO4CoFiD9w==
   dependencies:
     "@babel/generator" "^7.9.5"
     "@babel/template" "^7.8.6"
     "@babel/types" "^7.9.5"
-    ajv "4.9.0"
+    ajv "6.12.3"
     babel-plugin-macros "^2.8.0"
     dedent "0.6.0"
     gettext-parser "4.0.0-alpha.0"
@@ -2765,9 +2614,9 @@ babylon@^7.0.0-beta.30:
   integrity sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==
 
 bail@^1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.4.tgz#7181b66d508aa3055d3f6c13f0a0c720641dde9b"
-  integrity sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
+  integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==
 
 balanced-match@0.1.0:
   version "0.1.0"
@@ -2795,14 +2644,9 @@ banner-webpack-plugin@^0.2.3:
   integrity sha1-6d7p2WRMzvH9lw4R2CQIr/QikOs=
 
 base64-js@^1.0.2:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
-  integrity sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==
-
-base64url@2.0.0, base64url@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
-  integrity sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
 
 base@^0.11.1:
   version "0.11.2"
@@ -2823,9 +2667,9 @@ batch@0.6.1:
   integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
 
 bcrypt-pbkdf@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
-  integrity sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
+  integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
   dependencies:
     tweetnacl "^0.14.3"
 
@@ -2834,48 +2678,68 @@ big.js@^3.1.3:
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
   integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
 
+big.js@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+  integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
 binary-extensions@^1.0.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
-  integrity sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
+  integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
+
+binary-extensions@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
+  integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
 
-block-stream@*:
-  version "0.0.9"
-  resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
-  integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
+bindings@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
+  integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
   dependencies:
-    inherits "~2.0.0"
+    file-uri-to-path "1.0.0"
 
 bluebird@3.5.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
   integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=
 
+bluebird@3.7.1:
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
+  integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==
+
 bluebird@^3.3.3, bluebird@^3.4.7, bluebird@^3.5.1:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
-  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+  integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
-  version "4.11.8"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
-  integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
+  version "4.11.9"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
+  integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
 
-body-parser@1.18.2:
-  version "1.18.2"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
-  integrity sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=
+bn.js@^5.1.1:
+  version "5.1.3"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
+  integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
+
+body-parser@1.19.0:
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
+  integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
   dependencies:
-    bytes "3.0.0"
+    bytes "3.1.0"
     content-type "~1.0.4"
     debug "2.6.9"
-    depd "~1.1.1"
-    http-errors "~1.6.2"
-    iconv-lite "0.4.19"
+    depd "~1.1.2"
+    http-errors "1.7.2"
+    iconv-lite "0.4.24"
     on-finished "~2.3.0"
-    qs "6.5.1"
-    raw-body "2.3.2"
-    type-is "~1.6.15"
+    qs "6.7.0"
+    raw-body "2.4.0"
+    type-is "~1.6.17"
 
 body@^5.1.0:
   version "5.1.0"
@@ -2911,24 +2775,10 @@ boom@2.x.x:
   dependencies:
     hoek "2.x.x"
 
-boom@4.x.x:
-  version "4.3.1"
-  resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
-  integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE=
-  dependencies:
-    hoek "4.x.x"
-
-boom@5.x.x:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
-  integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==
-  dependencies:
-    hoek "4.x.x"
-
 brace-expansion@^1.1.7:
-  version "1.1.8"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
-  integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI=
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
   dependencies:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
@@ -2942,14 +2792,13 @@ braces@^1.8.2:
     preserve "^0.2.0"
     repeat-element "^1.1.2"
 
-braces@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e"
-  integrity sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==
+braces@^2.3.1, braces@^2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
+  integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
   dependencies:
     arr-flatten "^1.1.0"
     array-unique "^0.3.2"
-    define-property "^1.0.0"
     extend-shallow "^2.0.1"
     fill-range "^4.0.0"
     isobject "^3.0.1"
@@ -2959,22 +2808,29 @@ braces@^2.3.0:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
+braces@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
 brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
 
 browser-resolve@^1.11.2, browser-resolve@^1.7.0:
-  version "1.11.2"
-  resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
-  integrity sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=
+  version "1.11.3"
+  resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
+  integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==
   dependencies:
     resolve "1.1.7"
 
 browserify-aes@^1.0.0, browserify-aes@^1.0.4:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
-  integrity sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
+  integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
   dependencies:
     buffer-xor "^1.0.3"
     cipher-base "^1.0.0"
@@ -2984,24 +2840,25 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4:
     safe-buffer "^5.0.1"
 
 browserify-cipher@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
-  integrity sha1-mYgkSHS/XtTijalWZtzWasj8Njo=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
+  integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
   dependencies:
     browserify-aes "^1.0.4"
     browserify-des "^1.0.0"
     evp_bytestokey "^1.0.0"
 
 browserify-des@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
-  integrity sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
+  integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==
   dependencies:
     cipher-base "^1.0.1"
     des.js "^1.0.0"
     inherits "^2.0.1"
+    safe-buffer "^5.1.2"
 
-browserify-rsa@^4.0.0:
+browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
   integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
@@ -3010,17 +2867,19 @@ browserify-rsa@^4.0.0:
     randombytes "^2.0.1"
 
 browserify-sign@^4.0.0:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
-  integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=
-  dependencies:
-    bn.js "^4.1.1"
-    browserify-rsa "^4.0.0"
-    create-hash "^1.1.0"
-    create-hmac "^1.1.2"
-    elliptic "^6.0.0"
-    inherits "^2.0.1"
-    parse-asn1 "^5.0.0"
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
+  integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
+  dependencies:
+    bn.js "^5.1.1"
+    browserify-rsa "^4.0.1"
+    create-hash "^1.2.0"
+    create-hmac "^1.1.7"
+    elliptic "^6.5.3"
+    inherits "^2.0.4"
+    parse-asn1 "^5.1.5"
+    readable-stream "^3.6.0"
+    safe-buffer "^5.2.0"
 
 browserify-zlib@^0.2.0:
   version "0.2.0"
@@ -3037,14 +2896,15 @@ browserslist@^1.0.0, browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7
     caniuse-db "^1.0.30000639"
     electron-to-chromium "^1.2.7"
 
-browserslist@^4.6.0, browserslist@^4.7.2:
-  version "4.7.2"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.2.tgz#1bb984531a476b5d389cedecb195b2cd69fb1348"
-  integrity sha512-uZavT/gZXJd2UTi9Ov7/Z340WOSQ3+m1iBVRUknf+okKxonL9P83S3ctiBDtuRmRu8PiCHjqyueqQ9HYlJhxiw==
+browserslist@^4.12.0, browserslist@^4.8.5:
+  version "4.14.5"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015"
+  integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==
   dependencies:
-    caniuse-lite "^1.0.30001004"
-    electron-to-chromium "^1.3.295"
-    node-releases "^1.1.38"
+    caniuse-lite "^1.0.30001135"
+    electron-to-chromium "^1.3.571"
+    escalade "^3.1.0"
+    node-releases "^1.1.61"
 
 bser@1.0.2:
   version "1.0.2"
@@ -3053,10 +2913,10 @@ bser@1.0.2:
   dependencies:
     node-int64 "^0.4.0"
 
-bser@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
-  integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=
+bser@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
+  integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==
   dependencies:
     node-int64 "^0.4.0"
 
@@ -3091,9 +2951,9 @@ buffer-xor@^1.0.3:
   integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
 
 buffer@^4.3.0:
-  version "4.9.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
-  integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=
+  version "4.9.2"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
+  integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
   dependencies:
     base64-js "^1.0.2"
     ieee754 "^1.1.4"
@@ -3116,11 +2976,6 @@ buffered-spawn@~1.1.1:
     err-code "^0.1.0"
     q "^1.0.1"
 
-builtin-modules@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-  integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -3136,7 +2991,12 @@ bytes@3.0.0:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
   integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
 
-cacache@^10.0.1:
+bytes@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
+  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+
+cacache@^10.0.4:
   version "10.0.4"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
   integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==
@@ -3177,6 +3037,13 @@ cachedir@1.3.0:
   dependencies:
     os-homedir "^1.0.1"
 
+caller-callsite@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
+  integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
+  dependencies:
+    callsites "^2.0.0"
+
 caller-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -3184,6 +3051,13 @@ caller-path@^0.1.0:
   dependencies:
     callsites "^0.2.0"
 
+caller-path@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
+  integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
+  dependencies:
+    caller-callsite "^2.0.0"
+
 callsites@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
@@ -3235,6 +3109,11 @@ camelcase@^4.1.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
   integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
 
+camelcase@^5.0.0:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
 camelize@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
@@ -3251,14 +3130,14 @@ caniuse-api@^1.5.2, caniuse-api@^1.5.3:
     lodash.uniq "^4.5.0"
 
 caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000789"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000789.tgz#5cf3fec75480041ab162ca06413153141e234325"
-  integrity sha1-XPP+x1SABBqxYsoGQTFTFB4jQyU=
+  version "1.0.30001148"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001148.tgz#15194b2c664f4bdd0128f2fa174601fcb47d9183"
+  integrity sha512-IngAS9iePELYyzIdxbqPm1+UEP3wso1mHUQiCXsHb0y3ab47FbgfBWURpe7TS07rUGUxNxY097+2hGKW+2wmIw==
 
-caniuse-lite@^1.0.30001004:
-  version "1.0.30001008"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001008.tgz#b8841b1df78a9f5ed9702537ef592f1f8772c0d9"
-  integrity sha512-b8DJyb+VVXZGRgJUa30cbk8gKHZ3LOZTBLaUEEVr2P4xpmFigOCc62CO4uzquW641Ouq1Rm9N+rWLWdSYDaDIw==
+caniuse-lite@^1.0.30001135:
+  version "1.0.30001148"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz#dc97c7ed918ab33bf8706ddd5e387287e015d637"
+  integrity sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==
 
 caseless@~0.11.0:
   version "0.11.0"
@@ -3271,9 +3150,9 @@ caseless@~0.12.0:
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
 ccount@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.2.tgz#53b6a2f815bb77b9c2871f7b9a72c3a25f1d8e89"
-  integrity sha1-U7ai+BW7d7nChx97mnLDol8djok=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17"
+  integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==
 
 center-align@^0.1.1:
   version "0.1.3"
@@ -3284,22 +3163,11 @@ center-align@^0.1.1:
     lazy-cache "^1.0.3"
 
 chain-function@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
-  integrity sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w=
-
-chalk@0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174"
-  integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=
-  dependencies:
-    ansi-styles "^1.1.0"
-    escape-string-regexp "^1.0.0"
-    has-ansi "^0.1.0"
-    strip-ansi "^0.3.0"
-    supports-color "^0.2.0"
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
+  integrity sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==
 
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -3319,6 +3187,14 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
+chalk@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+  integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
 chalk@^4.0.0, chalk@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
@@ -3342,24 +3218,24 @@ change-emitter@^0.1.2:
   integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=
 
 character-entities-html4@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.1.tgz#359a2a4a0f7e29d3dc2ac99bdbe21ee39438ea50"
-  integrity sha1-NZoqSg9+KdPcKsmb2+Ie45Q46lA=
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125"
+  integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==
 
 character-entities-legacy@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz#3c729991d9293da0ede6dddcaf1f2ce1009ee8b4"
-  integrity sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
+  integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
 
 character-entities@^1.0.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.3.tgz#bbed4a52fe7ef98cc713c6d80d9faa26916d54e6"
-  integrity sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
+  integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
 
 character-reference-invalid@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz#1647f4f726638d3ea4a750cf5d1975c1c7919a85"
-  integrity sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
+  integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
 
 check-more-types@2.24.0:
   version "2.24.0"
@@ -3388,13 +3264,13 @@ cheerio@^0.22.0:
     lodash.reject "^4.4.0"
     lodash.some "^4.4.0"
 
-cheerio@^1.0.0-rc.2:
-  version "1.0.0-rc.2"
-  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
-  integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=
+cheerio@^1.0.0-rc.3:
+  version "1.0.0-rc.3"
+  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
+  integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
   dependencies:
     css-select "~1.2.0"
-    dom-serializer "~0.1.0"
+    dom-serializer "~0.1.1"
     entities "~1.1.1"
     htmlparser2 "^3.9.1"
     lodash "^4.15.0"
@@ -3407,7 +3283,7 @@ chevrotain@^6.5.0:
   dependencies:
     regexp-to-ast "0.4.0"
 
-chokidar@^1.2.0, chokidar@^1.6.1, chokidar@^1.7.0:
+chokidar@^1.2.0, chokidar@^1.6.1:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
   integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=
@@ -3423,33 +3299,44 @@ chokidar@^1.2.0, chokidar@^1.6.1, chokidar@^1.7.0:
   optionalDependencies:
     fsevents "^1.0.0"
 
-chokidar@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.0.tgz#6686313c541d3274b2a5c01233342037948c911b"
-  integrity sha512-OgXCNv2U6TnG04D3tth0gsvdbV4zdbxFG3sYUqcoQMoEFVd1j1pZR6TZ8iknC45o9IJ6PeQI/J6wT/+cHcniAw==
+chokidar@^2.1.2, chokidar@^2.1.8:
+  version "2.1.8"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
+  integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
   dependencies:
     anymatch "^2.0.0"
-    async-each "^1.0.0"
-    braces "^2.3.0"
+    async-each "^1.0.1"
+    braces "^2.3.2"
     glob-parent "^3.1.0"
-    inherits "^2.0.1"
+    inherits "^2.0.3"
     is-binary-path "^1.0.0"
     is-glob "^4.0.0"
-    normalize-path "^2.1.1"
+    normalize-path "^3.0.0"
     path-is-absolute "^1.0.0"
-    readdirp "^2.0.0"
+    readdirp "^2.2.1"
+    upath "^1.1.1"
   optionalDependencies:
-    fsevents "^1.0.0"
+    fsevents "^1.2.7"
+
+chokidar@^3.4.1:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
+  integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.5.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
 
 chownr@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
-  integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=
-
-ci-info@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4"
-  integrity sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+  integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
 
 ci-info@^1.5.0:
   version "1.6.0"
@@ -3477,14 +3364,13 @@ clap@^1.0.9:
     chalk "^1.1.3"
 
 class-utils@^0.3.5:
-  version "0.3.5"
-  resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80"
-  integrity sha1-F+eTEDdQ+WJ7IXbqNM/RtWWQPIA=
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
+  integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
   dependencies:
     arr-union "^3.1.0"
     define-property "^0.2.5"
     isobject "^3.0.0"
-    lazy-cache "^2.0.2"
     static-extend "^0.1.1"
 
 classlist-polyfill@^1.2.0:
@@ -3492,22 +3378,17 @@ classlist-polyfill@^1.2.0:
   resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e"
   integrity sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=
 
-classnames@^2.1.3, classnames@^2.2.3:
-  version "2.2.5"
-  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
-  integrity sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=
-
-classnames@^2.2.5:
+classnames@^2.1.3, classnames@^2.2.5:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
   integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
 
-clean-css@4.1.x:
-  version "4.1.9"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301"
-  integrity sha1-Nc7ornaHpJuYA09w3gDE7dOCYwE=
+clean-css@4.2.x:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
+  integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==
   dependencies:
-    source-map "0.5.x"
+    source-map "~0.6.0"
 
 clean-tag@^1.0.3:
   version "1.1.0"
@@ -3539,9 +3420,9 @@ cli-truncate@^0.2.1:
     string-width "^1.0.1"
 
 cli-width@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
-  integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
+  integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
 
 cliui@^2.1.0:
   version "2.1.0"
@@ -3561,6 +3442,15 @@ cliui@^3.2.0:
     strip-ansi "^3.0.1"
     wrap-ansi "^2.0.0"
 
+cliui@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+  integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^6.2.0"
+
 clone-buffer@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
@@ -3577,23 +3467,28 @@ clone-stats@^1.0.0:
   integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=
 
 clone@^1.0.0, clone@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
-  integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
+  integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
 
 clone@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb"
-  integrity sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+  integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
 
 cloneable-readable@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117"
-  integrity sha1-pikNQT8hemEjL5XkWP84QYz7ARc=
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec"
+  integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==
   dependencies:
     inherits "^2.0.1"
-    process-nextick-args "^1.0.6"
-    through2 "^2.0.1"
+    process-nextick-args "^2.0.0"
+    readable-stream "^2.3.5"
+
+clsx@^1.0.4:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
+  integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
 
 co@^4.6.0:
   version "4.6.0"
@@ -3612,15 +3507,10 @@ code-point-at@^1.0.0:
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
 
-collapse-white-space@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c"
-  integrity sha1-S5BvZw5aljqHt2sOFolkM0G2Ajw=
-
-collapse-white-space@^1.0.2:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.5.tgz#c2495b699ab1ed380d29a1091e01063e75dbbe3a"
-  integrity sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==
+collapse-white-space@^1.0.0, collapse-white-space@^1.0.2:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287"
+  integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==
 
 collection-visit@^1.0.0:
   version "1.0.0"
@@ -3650,9 +3540,9 @@ color-convert@^2.0.1:
     color-name "~1.1.4"
 
 color-diff@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/color-diff/-/color-diff-1.1.0.tgz#983ae7f936679e94e365dfe44a16aa153bdae88e"
-  integrity sha1-mDrn+TZnnpTjZd/kShaqFTva6I4=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/color-diff/-/color-diff-1.2.0.tgz#01a7c806de47dbd5ae571da49e65489666c999a7"
+  integrity sha512-FN7iLBCfb97ElJU2AQXbBAFXPbKmu0XJjPU9GWWmUkIbXka+Im8Q5w1geiL9GB+AktJ4pIA6nRZD1+TlEG6/rA==
 
 color-harmony@^0.3.0:
   version "0.3.0"
@@ -3678,10 +3568,10 @@ color-string@^0.3.0:
   dependencies:
     color-name "^1.0.0"
 
-color-string@^1.5.2:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
-  integrity sha1-JuRYFLw8mny9Z1FkikFDRRSnc6k=
+color-string@^1.5.4:
+  version "1.5.4"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6"
+  integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==
   dependencies:
     color-name "^1.0.0"
     simple-swizzle "^0.2.2"
@@ -3704,12 +3594,12 @@ color@^0.11.0, color@^0.11.3, color@^0.11.4:
     color-string "^0.3.0"
 
 color@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a"
-  integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
+  integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
   dependencies:
     color-convert "^1.9.1"
-    color-string "^1.5.2"
+    color-string "^1.5.4"
 
 colormin@^1.0.5:
   version "1.1.2"
@@ -3731,18 +3621,11 @@ colors@1.1.2, colors@~1.1.2:
   integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
 
 colors@^1.1.2:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
-  integrity sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg==
-
-combined-stream@^1.0.5, combined-stream@~1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
-  integrity sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=
-  dependencies:
-    delayed-stream "~1.0.0"
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
 
-combined-stream@^1.0.6, combined-stream@~1.0.6:
+combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
   integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -3750,31 +3633,39 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
     delayed-stream "~1.0.0"
 
 comma-separated-tokens@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.4.tgz#72083e58d4a462f01866f6617f4d98a3cd3b8a46"
-  integrity sha1-cgg+WNSkYvAYZvZhf02Yo807ikY=
-  dependencies:
-    trim "0.0.1"
-
-commander@2.12.x, commander@~2.12.1:
-  version "2.12.2"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
-  integrity sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
+  integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
 
 commander@2.15.1:
   version "2.15.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
   integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
 
+commander@2.17.x:
+  version "2.17.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
+  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
+
 commander@2.6.0:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
   integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=
 
 commander@^2.11.0, commander@^2.19.0, commander@^2.5.0, commander@^2.9.0:
-  version "2.20.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
-  integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
+  version "2.20.3"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@~2.13.0:
+  version "2.13.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
+  integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==
+
+commander@~2.19.0:
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
+  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
 
 commander@~2.9.0:
   version "2.9.0"
@@ -3809,28 +3700,28 @@ commoner@^0.10.8:
     recast "^0.11.17"
 
 component-emitter@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
+  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
 
-compressible@~2.0.11:
-  version "2.0.12"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66"
-  integrity sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=
+compressible@~2.0.16:
+  version "2.0.18"
+  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
+  integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
   dependencies:
-    mime-db ">= 1.30.0 < 2"
+    mime-db ">= 1.43.0 < 2"
 
-compression@^1.5.2:
-  version "1.7.1"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db"
-  integrity sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=
+compression@^1.7.3:
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+  integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
   dependencies:
-    accepts "~1.3.4"
+    accepts "~1.3.5"
     bytes "3.0.0"
-    compressible "~2.0.11"
+    compressible "~2.0.16"
     debug "2.6.9"
-    on-headers "~1.0.1"
-    safe-buffer "5.1.1"
+    on-headers "~1.0.2"
+    safe-buffer "5.1.2"
     vary "~1.1.2"
 
 concat-map@0.0.1:
@@ -3838,7 +3729,7 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@1.6.2:
+concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.5.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -3848,15 +3739,6 @@ concat-stream@1.6.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
-concat-stream@^1.5.0, concat-stream@^1.5.2:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
-  integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=
-  dependencies:
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
 concat-stream@~1.5.0:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
@@ -3867,35 +3749,29 @@ concat-stream@~1.5.0:
     typedarray "~0.0.5"
 
 concurrently@^3.1.0:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.1.tgz#ee8b60018bbe86b02df13e5249453c6ececd2521"
-  integrity sha512-689HrwGw8Rbk1xtV9C4dY6TPJAvIYZbRbnKSAtfJ7tHqICFGoZ0PCWYjxfmerRyxBG0o3sbG3pe7N8vqPwIHuQ==
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.1.tgz#2f95baec5c4051294dfbb55b57a3b98a3e2b45ec"
+  integrity sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q==
   dependencies:
-    chalk "0.5.1"
+    chalk "^2.4.1"
     commander "2.6.0"
     date-fns "^1.23.0"
     lodash "^4.5.1"
+    read-pkg "^3.0.0"
     rx "2.3.24"
     spawn-command "^0.0.2-1"
     supports-color "^3.2.3"
     tree-kill "^1.1.0"
 
 connect-history-api-fallback@^1.3.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a"
-  integrity sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
+  integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
 
 console-browserify@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
-  integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=
-  dependencies:
-    date-now "^0.1.4"
-
-console-control-strings@^1.0.0, console-control-strings@~1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
+  integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
 
 constants-browserify@^1.0.0:
   version "1.0.0"
@@ -3907,10 +3783,12 @@ contains-path@^0.1.0:
   resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
   integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
 
-content-disposition@0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
-  integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
+content-disposition@0.5.3:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
+  integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+  dependencies:
+    safe-buffer "5.1.2"
 
 content-type-parser@^1.0.1:
   version "1.0.2"
@@ -3927,14 +3805,7 @@ continuable-cache@^0.3.1:
   resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f"
   integrity sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=
 
-convert-source-map@^1.1.0, convert-source-map@^1.1.1, convert-source-map@^1.5.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
-  integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-convert-source-map@^1.5.1, convert-source-map@^1.7.0:
+convert-source-map@^1.1.1, convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
   integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
@@ -3946,10 +3817,10 @@ cookie-signature@1.0.6:
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
   integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
 
-cookie@0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
-  integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
+cookie@0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
+  integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
 
 copy-concurrently@^1.0.0:
   version "1.0.5"
@@ -3969,19 +3840,19 @@ copy-descriptor@^0.1.0:
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
 copy-to-clipboard@^3:
-  version "3.0.8"
-  resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
-  integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
+  integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
   dependencies:
-    toggle-selection "^1.0.3"
+    toggle-selection "^1.0.6"
 
-core-js-compat@^3.1.1:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.4.0.tgz#2a47c51d3dc026d290018cacd987495f68a47c75"
-  integrity sha512-pgQUcgT2+v9/yxHgMynYjNj7nmxLRXv3UC39rjCjDwpe63ev2rioQTju1PKLYUBbPCQQvZNWvQC8tBJd65q11g==
+core-js-compat@^3.6.2:
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c"
+  integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==
   dependencies:
-    browserslist "^4.7.2"
-    semver "^6.3.0"
+    browserslist "^4.8.5"
+    semver "7.0.0"
 
 core-js-pure@^3.0.0:
   version "3.6.5"
@@ -3994,14 +3865,14 @@ core-js@^1.0.0:
   integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
 
 core-js@^2.4.0, core-js@^2.5.0:
-  version "2.5.3"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
-  integrity sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
+  integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
 
 core-js@^3.1.4:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
-  integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
+  integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -4022,18 +3893,15 @@ cosmiconfig@^1.1.0:
     pinkie-promise "^2.0.0"
     require-from-string "^1.1.0"
 
-cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892"
-  integrity sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==
+cosmiconfig@^5.0.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
+  integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
   dependencies:
+    import-fresh "^2.0.0"
     is-directory "^0.3.1"
-    js-yaml "^3.4.3"
-    minimist "^1.2.0"
-    object-assign "^4.1.0"
-    os-homedir "^1.0.1"
-    parse-json "^2.2.0"
-    require-from-string "^1.1.0"
+    js-yaml "^3.13.1"
+    parse-json "^4.0.0"
 
 cosmiconfig@^6.0.0:
   version "6.0.0"
@@ -4047,27 +3915,28 @@ cosmiconfig@^6.0.0:
     yaml "^1.7.2"
 
 create-ecdh@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
-  integrity sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
+  integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
   dependencies:
     bn.js "^4.1.0"
-    elliptic "^6.0.0"
+    elliptic "^6.5.3"
 
-create-hash@^1.1.0, create-hash@^1.1.2:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
-  integrity sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=
+create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
+  integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
   dependencies:
     cipher-base "^1.0.1"
     inherits "^2.0.1"
-    ripemd160 "^2.0.0"
+    md5.js "^1.3.4"
+    ripemd160 "^2.0.1"
     sha.js "^2.4.0"
 
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
-  integrity sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=
+create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
+  integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
   dependencies:
     cipher-base "^1.0.3"
     create-hash "^1.1.0"
@@ -4077,11 +3946,10 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     sha.js "^2.4.8"
 
 create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.6.0:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a"
-  integrity sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=
+  version "15.7.0"
+  resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
+  integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
   dependencies:
-    fbjs "^0.8.9"
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
@@ -4130,13 +3998,6 @@ cryptiles@2.x.x:
   dependencies:
     boom "2.x.x"
 
-cryptiles@3.x.x:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
-  integrity sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=
-  dependencies:
-    boom "5.x.x"
-
 crypto-browserify@^3.11.0:
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -4175,9 +4036,9 @@ css-color-names@0.0.4:
   integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
 
 css-loader@^0.28.7:
-  version "0.28.8"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.8.tgz#ff36381464dea18fe60f2601a060ba6445886bd5"
-  integrity sha512-4jGj7Ag6WUZ5lQyE4te9sJLn0lgkz6HI3WDE4aw98AkW1IAKXPP4blTpPeorlLDpNsYvojo0SYgRJOdz2KbuAw==
+  version "0.28.11"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
+  integrity sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==
   dependencies:
     babel-code-frame "^6.26.0"
     css-selector-tokenizer "^0.7.0"
@@ -4187,7 +4048,7 @@ css-loader@^0.28.7:
     lodash.camelcase "^4.3.0"
     object-assign "^4.1.1"
     postcss "^5.0.6"
-    postcss-modules-extract-imports "^1.1.0"
+    postcss-modules-extract-imports "^1.2.0"
     postcss-modules-local-by-default "^1.2.0"
     postcss-modules-scope "^1.1.0"
     postcss-modules-values "^1.3.0"
@@ -4205,13 +4066,12 @@ css-select@^1.1.0, css-select@~1.2.0:
     nth-check "~1.0.1"
 
 css-selector-tokenizer@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
-  integrity sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1"
+  integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==
   dependencies:
-    cssesc "^0.1.0"
-    fastparse "^1.1.1"
-    regexpu-core "^1.0.0"
+    cssesc "^3.0.0"
+    fastparse "^1.1.2"
 
 css-to-react-native@^2.0.3:
   version "2.3.2"
@@ -4223,9 +4083,9 @@ css-to-react-native@^2.0.3:
     postcss-value-parser "^3.3.0"
 
 css-what@2.1:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
-  integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0=
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
+  integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
 
 css.escape@^1.5.1:
   version "1.5.1"
@@ -4242,10 +4102,10 @@ css@^2.2.3:
     source-map-resolve "^0.5.2"
     urix "^0.1.0"
 
-cssesc@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
-  integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=
+cssesc@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+  integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
 
 cssnano@^3.10.0:
   version "3.10.0"
@@ -4294,9 +4154,9 @@ csso@~2.3.1:
     source-map "^0.5.3"
 
 cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
-  integrity sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=
+  version "0.3.8"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+  integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
 
 "cssstyle@>= 0.2.37 < 0.3.0":
   version "0.2.37"
@@ -4305,7 +4165,12 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
   dependencies:
     cssom "0.3.x"
 
-cuint@latest:
+csstype@^3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
+  integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==
+
+cuint@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
   integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=
@@ -4322,12 +4187,12 @@ cycle@1.0.x:
   resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
   integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
 
-cyclist@~0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
-  integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
+cyclist@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
+  integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
 
-cypress@3.8.2, cypress@^3.5.0:
+cypress@*, cypress@3.8.2:
   version "3.8.2"
   resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.2.tgz#58fa96e1e7dae712403b0f4e8af1efe35442ff7a"
   integrity sha512-aTs0u3+dfEuLe0Ct0FVO5jD1ULqxbuqWUZwzBm0rxdLgLxIAOI/A9f/WkgY5Cfy1TEXe8pKC6Wal0ZpnkdGRSw==
@@ -4367,36 +4232,36 @@ cypress@3.8.2, cypress@^3.5.0:
     yauzl "2.10.0"
 
 d3-array@^1.2.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc"
-  integrity sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
+  integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
 
 d3-collection@1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2"
-  integrity sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
+  integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
 
 d3-color@1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a"
-  integrity sha512-dmL9Zr/v39aSSMnLOTd58in2RbregCg4UtGyUArvEKTTN6S3HKEy+ziBWVYo9PTzRyVW+pUBHUtRKz0HYX+SQg==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
+  integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
 
 d3-format@1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11"
-  integrity sha512-ycfLEIzHVZC3rOvuBOKVyQXSiUyCDjeAPIj9n/wugrr+s5AcTQC2Bz6aKkubG7rQaQF0SGW/OV4UEJB9nfioFg==
+  version "1.4.5"
+  resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
+  integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
 
 d3-interpolate@1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41"
-  integrity sha512-zLvTk8CREPFfc/2XglPQriAsXkzoRDAyBzndtKJWrZmHw7kmOWHNS11e40kPTd/oGk8P5mFJW5uBbcFQ+ybxyA==
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
+  integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
   dependencies:
     d3-color "1"
 
 d3-scale@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.1.0.tgz#8d3fd3e2a7c9080782a523c08507c5248289eef8"
-  integrity sha512-Bb2N3ZgzPdKVEoWGkt8lPV6R7YdpSBWI70Xf26NQHOVjs77a6gLUmBOOPt9d9nB8JiQhwXY1RHCa+eSyWCJZIQ==
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
+  integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==
   dependencies:
     d3-array "^1.2.0"
     d3-collection "1"
@@ -4406,28 +4271,29 @@ d3-scale@^2.1.0:
     d3-time-format "2"
 
 d3-time-format@2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31"
-  integrity sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850"
+  integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==
   dependencies:
     d3-time "1"
 
 d3-time@1:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84"
-  integrity sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
+  integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
 
 d3@^3, d3@^3.5.17:
   version "3.5.17"
   resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
   integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=
 
-d@1:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
-  integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=
+d@1, d@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+  integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
   dependencies:
-    es5-ext "^0.10.9"
+    es5-ext "^0.10.50"
+    type "^1.0.1"
 
 dashdash@^1.12.0:
   version "1.14.1"
@@ -4437,14 +4303,9 @@ dashdash@^1.12.0:
     assert-plus "^1.0.0"
 
 date-fns@^1.23.0, date-fns@^1.27.2:
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
-  integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
-
-date-now@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-  integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
+  version "1.30.1"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
+  integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
 
 dc@2.1.9:
   version "2.1.9"
@@ -4454,28 +4315,28 @@ dc@2.1.9:
     crossfilter2 "~1.3"
     d3 "^3"
 
-debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.7:
+debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-debug@3.1.0, debug@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
-  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
-  dependencies:
-    ms "2.0.0"
-
-debug@3.2.6:
+debug@3.2.6, debug@^3.1.0, debug@^3.1.1:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
   dependencies:
     ms "^2.1.1"
 
-debug@^4.1.0:
+debug@4, debug@^4.1.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
+  integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
+  dependencies:
+    ms "2.1.2"
+
+debug@4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
   integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -4489,7 +4350,7 @@ debug@~2.2.0:
   dependencies:
     ms "0.7.1"
 
-decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
+decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -4515,14 +4376,16 @@ deep-diff@^0.3.5:
   integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=
 
 deep-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
-  integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
-
-deep-extend@~0.4.0:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
-  integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
+  integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
+  dependencies:
+    is-arguments "^1.0.4"
+    is-date-object "^1.0.1"
+    is-regex "^1.0.4"
+    object-is "^1.0.1"
+    object-keys "^1.1.1"
+    regexp.prototype.flags "^1.2.0"
 
 deep-is@~0.1.3:
   version "0.1.3"
@@ -4536,7 +4399,7 @@ default-require-extensions@^1.0.0:
   dependencies:
     strip-bom "^2.0.0"
 
-define-properties@^1.1.2, define-properties@^1.1.3:
+define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
   integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@@ -4557,24 +4420,19 @@ define-property@^1.0.0:
   dependencies:
     is-descriptor "^1.0.0"
 
+define-property@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
+  integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==
+  dependencies:
+    is-descriptor "^1.0.2"
+    isobject "^3.0.1"
+
 defined@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
   integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
 
-del@^2.0.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
-  integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=
-  dependencies:
-    globby "^5.0.0"
-    is-path-cwd "^1.0.0"
-    is-path-in-cwd "^1.0.0"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-    rimraf "^2.2.8"
-
 del@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
@@ -4592,20 +4450,15 @@ delayed-stream@~1.0.0:
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
 
-delegates@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
-
-depd@1.1.1, depd@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
-  integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
+depd@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+  integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
 
 des.js@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
-  integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
+  integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==
   dependencies:
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
@@ -4616,9 +4469,9 @@ destroy@~1.0.4:
   integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
 
 detab@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.1.tgz#531f5e326620e2fd4f03264a905fb3bcc8af4df4"
-  integrity sha512-/hhdqdQc5thGrqzjyO/pz76lDZ5GSuAs6goxOaKTsvPk7HNnzAyFN5lyHgqpX4/s1i66K8qMGj+VhA9504x7DQ==
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.3.tgz#33e5dd74d230501bd69985a0d2b9a3382699a130"
+  integrity sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==
   dependencies:
     repeat-string "^1.5.4"
 
@@ -4629,15 +4482,10 @@ detect-indent@^4.0.0:
   dependencies:
     repeating "^2.0.0"
 
-detect-libc@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
-
-detect-node@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
-  integrity sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=
+detect-node@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
+  integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
 detective@^4.0.0, detective@^4.3.1:
   version "4.7.1"
@@ -4647,10 +4495,10 @@ detective@^4.0.0, detective@^4.3.1:
     acorn "^5.2.1"
     defined "^1.0.0"
 
-diff-sequences@^24.3.0:
-  version "24.3.0"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
-  integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
+diff-sequences@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
+  integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
 
 diff@^1.3.2:
   version "1.4.0"
@@ -4658,14 +4506,14 @@ diff@^1.3.2:
   integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8=
 
 diff@^3.0.0, diff@^3.2.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
-  integrity sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+  integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
 
 diffie-hellman@^5.0.0:
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
-  integrity sha1-tYNXOScM/ias9jIJn97SoH8gnl4=
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
+  integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
   dependencies:
     bn.js "^4.1.0"
     miller-rabin "^4.0.0"
@@ -4708,10 +4556,10 @@ dns-equal@^1.0.0:
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
   integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
 
-dns-packet@^1.0.1:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a"
-  integrity sha512-kN+DjfGF7dJGUL7nWRktL9Z18t1rWP3aQlyZdY8XlpvU3Nc6GeFTQApftcjtWKxAZfiggZSGrCEoszNgvnpwDg==
+dns-packet@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
+  integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==
   dependencies:
     ip "^1.1.0"
     safe-buffer "^5.0.1"
@@ -4801,69 +4649,78 @@ documentation@^4.0.0-rc.1:
     vinyl-fs "^2.3.1"
     yargs "^6.0.1"
 
+dom-accessibility-api@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz#511e5993dd673b97c87ea47dba0e3892f7e0c983"
+  integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA==
+
 dom-accessibility-api@^0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz#ef3cdb5d3f0d599d8f9c8b18df2fb63c9793739d"
-  integrity sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA==
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
+  integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==
 
-dom-converter@~0.1:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b"
-  integrity sha1-pF71cnuJDJv/5tfIduexnLDhfzs=
+dom-converter@^0.2:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
+  integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
   dependencies:
-    utila "~0.3"
+    utila "~0.4"
 
-"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.2.0:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
-  integrity sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==
+dom-helpers@^3.2.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
+  integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
+  dependencies:
+    "@babel/runtime" "^7.1.2"
 
-dom-serializer@0, dom-serializer@~0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
-  integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=
+dom-helpers@^5.1.3:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
+  integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
   dependencies:
-    domelementtype "~1.1.1"
-    entities "~1.1.1"
+    "@babel/runtime" "^7.8.7"
+    csstype "^3.0.2"
 
-dom-walk@^0.1.0:
+dom-serializer@0:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
+  integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
+  dependencies:
+    domelementtype "^2.0.1"
+    entities "^2.0.0"
+
+dom-serializer@~0.1.0, dom-serializer@~0.1.1:
   version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
-  integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+  integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+  dependencies:
+    domelementtype "^1.3.0"
+    entities "^1.1.1"
 
-domain-browser@^1.1.1:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
-  integrity sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=
+dom-walk@^0.1.0:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
+  integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
 
-domelementtype@1, domelementtype@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
-  integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=
+domain-browser@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
+  integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
 
-domelementtype@~1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
-  integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=
+domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
+  integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
 
-domhandler@2.1:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
-  integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=
-  dependencies:
-    domelementtype "1"
+domelementtype@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971"
+  integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==
 
 domhandler@^2.3.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259"
-  integrity sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=
-  dependencies:
-    domelementtype "1"
-
-domutils@1.1:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
-  integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
+  integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
   dependencies:
     domelementtype "1"
 
@@ -4876,9 +4733,9 @@ domutils@1.5.1:
     domelementtype "1"
 
 domutils@^1.5.1:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff"
-  integrity sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
+  integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
   dependencies:
     dom-serializer "0"
     domelementtype "1"
@@ -4890,10 +4747,10 @@ duplexer2@^0.1.2, duplexer2@~0.1.0:
   dependencies:
     readable-stream "^2.0.2"
 
-duplexify@^3.1.2, duplexify@^3.2.0, duplexify@^3.4.2:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd"
-  integrity sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==
+duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.6.0:
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+  integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
   dependencies:
     end-of-stream "^1.0.0"
     inherits "^2.0.1"
@@ -4901,18 +4758,18 @@ duplexify@^3.1.2, duplexify@^3.2.0, duplexify@^3.4.2:
     stream-shift "^1.0.0"
 
 ecc-jsbn@~0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
-  integrity sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
+  integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
   dependencies:
     jsbn "~0.1.0"
+    safer-buffer "^2.1.0"
 
-ecdsa-sig-formatter@1.0.9:
-  version "1.0.9"
-  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
-  integrity sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
   dependencies:
-    base64url "^2.0.0"
     safe-buffer "^5.0.1"
 
 ee-first@1.1.1:
@@ -4920,32 +4777,20 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-electron-releases@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/electron-releases/-/electron-releases-2.1.0.tgz#c5614bf811f176ce3c836e368a0625782341fd4e"
-  integrity sha512-cyKFD1bTE/UgULXfaueIN1k5EPFzs+FRc/rvCY5tIynefAPqopQEgjr0EzY+U3Dqrk/G4m9tXSPuZ77v6dL/Rw==
-
-electron-to-chromium@^1.2.7:
-  version "1.3.30"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.30.tgz#9666f532a64586651fc56a72513692e820d06a80"
-  integrity sha512-zx1Prv7kYLfc4OA60FhxGbSo4qrEjgSzpo1/37i7l9ltXPYOoQBtjQxY9KmsgfHnBxHlBGXwLlsbt/gub1w5lw==
-  dependencies:
-    electron-releases "^2.1.0"
-
-electron-to-chromium@^1.3.295:
-  version "1.3.306"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.306.tgz#e8265301d053d5f74e36cb876486830261fbe946"
-  integrity sha512-frDqXvrIROoYvikSKTIKbHbzO6M3/qC6kCIt/1FOa9kALe++c4VAJnwjSFvf1tYLEUsP2n9XZ4XSCyqc3l7A/A==
+electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.571:
+  version "1.3.582"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.582.tgz#1adfac5affce84d85b3d7b3dfbc4ade293a6ffc4"
+  integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww==
 
 elegant-spinner@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
   integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
 
-elliptic@^6.0.0:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
-  integrity sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=
+elliptic@^6.5.3:
+  version "6.5.3"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
+  integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
   dependencies:
     bn.js "^4.4.0"
     brorand "^1.0.1"
@@ -4960,27 +4805,37 @@ elliptic@^6.0.0:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e"
   integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=
 
+emoji-regex@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
   integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
 
-encodeurl@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
-  integrity sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=
+emojis-list@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
+  integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
+
+encodeurl@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
 
 encoding@^0.1.11, encoding@^0.1.12:
-  version "0.1.12"
-  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
-  integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
+  version "0.1.13"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
+  integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
   dependencies:
-    iconv-lite "~0.4.13"
+    iconv-lite "^0.6.2"
 
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206"
-  integrity sha1-epDYM+/abPpurA9JSduw+tOmMgY=
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+  integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
   dependencies:
     once "^1.4.0"
 
@@ -5004,9 +4859,14 @@ enhanced-resolve@~0.9.0:
     tapable "^0.1.8"
 
 entities@^1.1.1, entities@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
-  integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA=
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
+  integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
+
+entities@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
+  integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
 
 "enzyme-2@npm:enzyme@2":
   version "2.9.1"
@@ -5025,51 +4885,63 @@ entities@^1.1.1, entities@~1.1.1:
     uuid "^3.0.1"
 
 enzyme-adapter-react-15@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-15/-/enzyme-adapter-react-15-1.3.0.tgz#4b842e07b2e30e959da0d01ace0afd8e75495498"
-  integrity sha512-0L2LJpwlkf8hEJfHnFURf3K2xUsDr6fgkM+2wKPNvC9XUCHKOtK1aidO803MFy8LjqujCECbI2LKIE6asfi8NQ==
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-15/-/enzyme-adapter-react-15-1.4.2.tgz#b6ef7b54cce317c8c521495f3b350e73913c5164"
+  integrity sha512-IX0zWf/iqn538cWhu+SiLteD6U/PF2cDYOnAUwDYa+UnB4S7JKr3oBXGwPQRYeGA+RRj2ve+xZ/WGnx3Ycbsig==
+  dependencies:
+    enzyme-adapter-utils "^1.13.0"
+    object.assign "^4.1.0"
+    object.values "^1.1.1"
+    prop-types "^15.7.2"
+    react-is "^16.13.1"
+
+enzyme-adapter-utils@^1.13.0:
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.1.tgz#59c1b734b0927543e3d8dc477299ec957feb312d"
+  integrity sha512-5A9MXXgmh/Tkvee3bL/9RCAAgleHqFnsurTYCbymecO4ohvtNO5zqIhHxV370t7nJAwaCfkgtffarKpC0GPt0g==
   dependencies:
-    enzyme-adapter-utils "^1.10.0"
+    airbnb-prop-types "^2.16.0"
+    function.prototype.name "^1.1.2"
     object.assign "^4.1.0"
-    object.values "^1.1.0"
-    prop-types "^15.6.2"
-    react-is "^16.7.0"
+    object.fromentries "^2.0.2"
+    prop-types "^15.7.2"
+    semver "^5.7.1"
 
-enzyme-adapter-utils@^1.10.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz#5836169f68b9e8733cb5b69cad5da2a49e34f550"
-  integrity sha512-VnIXJDYVTzKGbdW+lgK8MQmYHJquTQZiGzu/AseCZ7eHtOMAj4Rtvk8ZRopodkfPves0EXaHkXBDkVhPa3t0jA==
+enzyme-shallow-equal@^1.0.1:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e"
+  integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==
   dependencies:
-    function.prototype.name "^1.1.0"
-    object.assign "^4.1.0"
-    object.fromentries "^2.0.0"
-    prop-types "^15.6.2"
-    semver "^5.6.0"
+    has "^1.0.3"
+    object-is "^1.1.2"
 
 enzyme@^3.8.0:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.8.0.tgz#646d2d5d0798cb98fdec39afcee8a53237b47ad5"
-  integrity sha512-bfsWo5nHyZm1O1vnIsbwdfhU989jk+squU9NKvB+Puwo5j6/Wg9pN5CO0YJelm98Dao3NPjkDZk+vvgwpMwYxw==
+  version "3.11.0"
+  resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28"
+  integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==
   dependencies:
-    array.prototype.flat "^1.2.1"
-    cheerio "^1.0.0-rc.2"
-    function.prototype.name "^1.1.0"
+    array.prototype.flat "^1.2.3"
+    cheerio "^1.0.0-rc.3"
+    enzyme-shallow-equal "^1.0.1"
+    function.prototype.name "^1.1.2"
     has "^1.0.3"
-    is-boolean-object "^1.0.0"
-    is-callable "^1.1.4"
-    is-number-object "^1.0.3"
-    is-string "^1.0.4"
+    html-element-map "^1.2.0"
+    is-boolean-object "^1.0.1"
+    is-callable "^1.1.5"
+    is-number-object "^1.0.4"
+    is-regex "^1.0.5"
+    is-string "^1.0.5"
     is-subset "^0.1.1"
     lodash.escape "^4.0.1"
     lodash.isequal "^4.5.0"
-    object-inspect "^1.6.0"
-    object-is "^1.0.1"
+    object-inspect "^1.7.0"
+    object-is "^1.0.2"
     object.assign "^4.1.0"
-    object.entries "^1.0.4"
-    object.values "^1.0.4"
-    raf "^3.4.0"
+    object.entries "^1.1.1"
+    object.values "^1.1.1"
+    raf "^3.4.1"
     rst-selector-parser "^2.2.3"
-    string.prototype.trim "^1.1.2"
+    string.prototype.trim "^1.2.1"
 
 err-code@^0.1.0:
   version "0.1.2"
@@ -5091,68 +4963,47 @@ error-ex@^1.2.0, error-ex@^1.3.1:
     is-arrayish "^0.2.1"
 
 error@^7.0.0:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
-  integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894"
+  integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==
   dependencies:
     string-template "~0.2.1"
-    xtend "~4.0.0"
-
-es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
-  integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
-  dependencies:
-    es-to-primitive "^1.2.0"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    is-callable "^1.1.4"
-    is-regex "^1.0.4"
-    object-keys "^1.0.12"
 
-es-abstract@^1.17.0:
-  version "1.17.4"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
-  integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
+es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5:
+  version "1.17.7"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
+  integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
   dependencies:
     es-to-primitive "^1.2.1"
     function-bind "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.1"
-    is-callable "^1.1.5"
-    is-regex "^1.0.5"
-    object-inspect "^1.7.0"
+    is-callable "^1.2.2"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
     object-keys "^1.1.1"
-    object.assign "^4.1.0"
-    string.prototype.trimleft "^2.1.1"
-    string.prototype.trimright "^2.1.1"
+    object.assign "^4.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
 
-es-abstract@^1.17.0-next.1, es-abstract@^1.17.5:
-  version "1.17.6"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
-  integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
+es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1:
+  version "1.18.0-next.1"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
+  integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
   dependencies:
     es-to-primitive "^1.2.1"
     function-bind "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.1"
-    is-callable "^1.2.0"
-    is-regex "^1.1.0"
-    object-inspect "^1.7.0"
+    is-callable "^1.2.2"
+    is-negative-zero "^2.0.0"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
     object-keys "^1.1.1"
-    object.assign "^4.1.0"
+    object.assign "^4.1.1"
     string.prototype.trimend "^1.0.1"
     string.prototype.trimstart "^1.0.1"
 
-es-to-primitive@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
-  integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
-  dependencies:
-    is-callable "^1.1.4"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.2"
-
 es-to-primitive@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -5162,15 +5013,16 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
-  version "0.10.37"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.37.tgz#0ee741d148b80069ba27d020393756af257defc3"
-  integrity sha1-DudB0Ui4AGm6J9AgOTdWryV978M=
+es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14:
+  version "0.10.53"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
+  integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
   dependencies:
-    es6-iterator "~2.0.1"
-    es6-symbol "~3.1.1"
+    es6-iterator "~2.0.3"
+    es6-symbol "~3.1.3"
+    next-tick "~1.0.0"
 
-es6-iterator@^2.0.1, es6-iterator@~2.0.1:
+es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
   integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
@@ -5207,7 +5059,7 @@ es6-set@~0.1.5:
     es6-symbol "3.1.1"
     event-emitter "~0.3.5"
 
-es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
+es6-symbol@3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
   integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
@@ -5215,37 +5067,50 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
     d "1"
     es5-ext "~0.10.14"
 
+es6-symbol@^3.1.1, es6-symbol@~3.1.1, es6-symbol@~3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
+  integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
+  dependencies:
+    d "^1.0.1"
+    ext "^1.1.2"
+
 es6-weak-map@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
-  integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
+  integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
   dependencies:
     d "1"
-    es5-ext "^0.10.14"
-    es6-iterator "^2.0.1"
+    es5-ext "^0.10.46"
+    es6-iterator "^2.0.3"
     es6-symbol "^3.1.1"
 
+escalade@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
 
-escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
 escodegen@^1.6.1:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
-  integrity sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==
+  version "1.14.3"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
+  integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
   dependencies:
-    esprima "^3.1.3"
+    esprima "^4.0.1"
     estraverse "^4.2.0"
     esutils "^2.0.2"
     optionator "^0.8.1"
   optionalDependencies:
-    source-map "~0.5.6"
+    source-map "~0.6.1"
 
 escope@^3.6.0:
   version "3.6.0"
@@ -5257,7 +5122,7 @@ escope@^3.6.0:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-import-resolver-node@^0.3.3:
+eslint-import-resolver-node@^0.3.4:
   version "0.3.4"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
   integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
@@ -5302,9 +5167,9 @@ eslint-module-utils@^2.6.0:
     pkg-dir "^2.0.0"
 
 eslint-plugin-cypress@^2.7.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.7.0.tgz#117f14ce63698e4c4f3afea3d7e27025c8d504f0"
-  integrity sha512-52Lq5ePCD/8jc536e1RqtLfj33BAy1s7BlYgCjbG39J5kqUitcTlRY5i3NRoeAyPHueDwETsq0eASF44ugLosQ==
+  version "2.11.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0"
+  integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA==
   dependencies:
     globals "^11.12.0"
 
@@ -5316,16 +5181,16 @@ eslint-plugin-flowtype@^2.50.3:
     lodash "^4.17.10"
 
 eslint-plugin-import@^2.22.0:
-  version "2.22.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
-  integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
+  version "2.22.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
+  integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==
   dependencies:
     array-includes "^3.1.1"
     array.prototype.flat "^1.2.3"
     contains-path "^0.1.0"
     debug "^2.6.9"
     doctrine "1.5.0"
-    eslint-import-resolver-node "^0.3.3"
+    eslint-import-resolver-node "^0.3.4"
     eslint-module-utils "^2.6.0"
     has "^1.0.3"
     minimatch "^3.0.4"
@@ -5335,9 +5200,9 @@ eslint-plugin-import@^2.22.0:
     tsconfig-paths "^3.9.0"
 
 eslint-plugin-jasmine@^2.2.0:
-  version "2.9.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.9.1.tgz#22e19a59f16f3a5f643a04aba04438d0e3047030"
-  integrity sha1-IuGaWfFvOl9kOgSroEQ40OMEcDA=
+  version "2.10.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz#5733b709e751f4bc40e31e1c16989bd2cdfbec97"
+  integrity sha1-VzO3CedR9LxA4x4cFpib0s377Jc=
 
 eslint-plugin-react@^6.10.3:
   version "6.10.3"
@@ -5392,11 +5257,11 @@ eslint@^3.19.0:
     user-home "^2.0.0"
 
 espree@^3.4.0:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca"
-  integrity sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==
+  version "3.5.4"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
+  integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==
   dependencies:
-    acorn "^5.2.1"
+    acorn "^5.5.0"
     acorn-jsx "^3.0.0"
 
 esprima@^2.6.0:
@@ -5404,40 +5269,44 @@ esprima@^2.6.0:
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
   integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=
 
-esprima@^3.1.3, esprima@~3.1.0:
+esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+  integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+esprima@~3.1.0:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
   integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
 
-esprima@^4.0.0, esprima@~4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
-  integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==
-
 esquery@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
-  integrity sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
+  integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
   dependencies:
-    estraverse "^4.0.0"
+    estraverse "^5.1.0"
 
 esrecurse@^4.1.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
-  integrity sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
   dependencies:
-    estraverse "^4.1.0"
-    object-assign "^4.0.1"
+    estraverse "^5.2.0"
 
-estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
-  integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
+estraverse@^4.1.1, estraverse@^4.2.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
+  integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
 
 esutils@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-  integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+  integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
 
 etag@~1.8.1:
   version "1.8.1"
@@ -5457,15 +5326,20 @@ eventemitter2@4.1.2:
   resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15"
   integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=
 
-eventemitter3@1.x.x, eventemitter3@^1.1.1:
+eventemitter3@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
   integrity sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=
 
-events@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
-  integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+eventemitter3@^4.0.0:
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+events@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
+  integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
 
 eventsource@0.1.6:
   version "0.1.6"
@@ -5483,11 +5357,11 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     safe-buffer "^5.1.1"
 
 exec-sh@^0.2.0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38"
-  integrity sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36"
+  integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==
   dependencies:
-    merge "^1.1.3"
+    merge "^1.2.0"
 
 execa@0.10.0:
   version "0.10.0"
@@ -5568,41 +5442,48 @@ exports-loader@^0.6.3:
     source-map "0.5.x"
 
 express@^4.16.2:
-  version "4.16.2"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
-  integrity sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=
+  version "4.17.1"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
+  integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
   dependencies:
-    accepts "~1.3.4"
+    accepts "~1.3.7"
     array-flatten "1.1.1"
-    body-parser "1.18.2"
-    content-disposition "0.5.2"
+    body-parser "1.19.0"
+    content-disposition "0.5.3"
     content-type "~1.0.4"
-    cookie "0.3.1"
+    cookie "0.4.0"
     cookie-signature "1.0.6"
     debug "2.6.9"
-    depd "~1.1.1"
-    encodeurl "~1.0.1"
+    depd "~1.1.2"
+    encodeurl "~1.0.2"
     escape-html "~1.0.3"
     etag "~1.8.1"
-    finalhandler "1.1.0"
+    finalhandler "~1.1.2"
     fresh "0.5.2"
     merge-descriptors "1.0.1"
     methods "~1.1.2"
     on-finished "~2.3.0"
-    parseurl "~1.3.2"
+    parseurl "~1.3.3"
     path-to-regexp "0.1.7"
-    proxy-addr "~2.0.2"
-    qs "6.5.1"
-    range-parser "~1.2.0"
-    safe-buffer "5.1.1"
-    send "0.16.1"
-    serve-static "1.13.1"
-    setprototypeof "1.1.0"
-    statuses "~1.3.1"
-    type-is "~1.6.15"
+    proxy-addr "~2.0.5"
+    qs "6.7.0"
+    range-parser "~1.2.1"
+    safe-buffer "5.1.2"
+    send "0.17.1"
+    serve-static "1.14.1"
+    setprototypeof "1.1.1"
+    statuses "~1.5.0"
+    type-is "~1.6.18"
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+ext@^1.1.2:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
+  integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
+  dependencies:
+    type "^2.0.0"
+
 extend-shallow@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -5610,7 +5491,7 @@ extend-shallow@^2.0.1:
   dependencies:
     is-extendable "^0.1.0"
 
-extend-shallow@^3.0.0:
+extend-shallow@^3.0.0, extend-shallow@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
   integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
@@ -5618,12 +5499,7 @@ extend-shallow@^3.0.0:
     assign-symbols "^1.0.0"
     is-extendable "^1.0.1"
 
-extend@3, extend@~3.0.0, extend@~3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
-  integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=
-
-extend@^3.0.0, extend@~3.0.2:
+extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -5635,10 +5511,10 @@ extglob@^0.3.1:
   dependencies:
     is-extglob "^1.0.0"
 
-extglob@^2.0.2:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.3.tgz#55e019d0c95bf873949c737b7e5172dba84ebb29"
-  integrity sha512-AyptZexgu7qppEPq59DtN/XJGZDrLcVxSHai+4hdgMMS9EpF4GBvygcWWApno8lL9qSjVpYt7Raao28qzJX1ww==
+extglob@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
+  integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
   dependencies:
     array-unique "^0.3.2"
     define-property "^1.0.0"
@@ -5685,9 +5561,9 @@ eyes@0.1.x:
   integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
 
 fast-deep-equal@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
-  integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
+  integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
 
 fast-deep-equal@^3.1.1:
   version "3.1.3"
@@ -5695,19 +5571,19 @@ fast-deep-equal@^3.1.1:
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
 fast-json-stable-stringify@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-  integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+  integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
-fast-levenshtein@~2.0.4:
+fast-levenshtein@~2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
-fastparse@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
-  integrity sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=
+fastparse@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
+  integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==
 
 faye-websocket@^0.10.0, faye-websocket@~0.10.0:
   version "0.10.0"
@@ -5717,9 +5593,9 @@ faye-websocket@^0.10.0, faye-websocket@~0.10.0:
     websocket-driver ">=0.5.1"
 
 faye-websocket@~0.11.0:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
-  integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=
+  version "0.11.3"
+  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
+  integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
   dependencies:
     websocket-driver ">=0.5.1"
 
@@ -5731,11 +5607,11 @@ fb-watchman@^1.8.0:
     bser "1.0.2"
 
 fb-watchman@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
-  integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
+  integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==
   dependencies:
-    bser "^2.0.0"
+    bser "2.1.1"
 
 fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9:
   version "0.8.17"
@@ -5787,6 +5663,11 @@ file-loader@^0.11.1:
   dependencies:
     loader-utils "^1.0.2"
 
+file-uri-to-path@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+  integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
 filename-regex@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@@ -5801,13 +5682,13 @@ fileset@^2.0.2:
     minimatch "^3.0.3"
 
 fill-range@^2.1.0:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
-  integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
+  integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==
   dependencies:
     is-number "^2.1.0"
     isobject "^2.0.0"
-    randomatic "^1.1.3"
+    randomatic "^3.0.0"
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
@@ -5821,17 +5702,24 @@ fill-range@^4.0.0:
     repeat-string "^1.6.1"
     to-regex-range "^2.1.0"
 
-finalhandler@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
-  integrity sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+finalhandler@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
+  integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
   dependencies:
     debug "2.6.9"
-    encodeurl "~1.0.1"
+    encodeurl "~1.0.2"
     escape-html "~1.0.3"
     on-finished "~2.3.0"
-    parseurl "~1.3.2"
-    statuses "~1.3.1"
+    parseurl "~1.3.3"
+    statuses "~1.5.0"
     unpipe "~1.0.0"
 
 find-cache-dir@^0.1.1:
@@ -5852,7 +5740,7 @@ find-cache-dir@^1.0.0:
     make-dir "^1.0.0"
     pkg-dir "^2.0.0"
 
-find-cache-dir@^2.0.0:
+find-cache-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
   integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@@ -5888,25 +5776,33 @@ find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
+find-up@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+  dependencies:
+    locate-path "^5.0.0"
+    path-exists "^4.0.0"
+
 first-chunk-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
   integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=
 
 flat-cache@^1.2.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
-  integrity sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f"
+  integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==
   dependencies:
     circular-json "^0.3.1"
-    del "^2.0.2"
     graceful-fs "^4.1.2"
+    rimraf "~2.6.2"
     write "^0.2.1"
 
 flatten@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
-  integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
+  integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
 
 flow-bin@^0.37.4:
   version "0.37.4"
@@ -5914,17 +5810,17 @@ flow-bin@^0.37.4:
   integrity sha1-PY2i73RugOcw0WbgkED0GYlpt2s=
 
 flow-parser@^0.*:
-  version "0.75.0"
-  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.75.0.tgz#9a1891c48051c73017b6b5cc07b3681fda3fdfb0"
-  integrity sha512-QEyV/t9TERBOSI/zSx0zhKH6924135WPI7pMmug2n/n/4puFm4mdAq1QaKPA3IPhXDRtManbySkKhRqws5UUGA==
+  version "0.136.0"
+  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.136.0.tgz#95e09bde3ba7f63f55c2c015cdd7cfd36ef8e652"
+  integrity sha512-PB2vYAqmz+dRikpx8TpNgRtBsyemP+7oQa0BcPZWnGABlJlB2WgJc/Lx0HeEPOUxDO/TxBbPaIHsffEIL9M6BQ==
 
 flush-write-stream@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
-  integrity sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
+  integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
   dependencies:
-    inherits "^2.0.1"
-    readable-stream "^2.0.4"
+    inherits "^2.0.3"
+    readable-stream "^2.3.6"
 
 flux-standard-action@^0.6.1:
   version "0.6.1"
@@ -5933,6 +5829,11 @@ flux-standard-action@^0.6.1:
   dependencies:
     lodash.isplainobject "^3.2.0"
 
+follow-redirects@^1.0.0:
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
+  integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
+
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -5959,15 +5860,6 @@ form-data@~2.1.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
-form-data@~2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
-  integrity sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.5"
-    mime-types "^2.1.12"
-
 form-data@~2.3.2:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -6049,60 +5941,37 @@ fs.realpath@^1.0.0:
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
-fsevents@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8"
-  integrity sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==
-  dependencies:
-    nan "^2.3.0"
-    node-pre-gyp "^0.6.39"
-
-fstream-ignore@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
-  integrity sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=
+fsevents@^1.0.0, fsevents@^1.2.7:
+  version "1.2.13"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
+  integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
   dependencies:
-    fstream "^1.0.0"
-    inherits "2"
-    minimatch "^3.0.0"
+    bindings "^1.5.0"
+    nan "^2.12.1"
 
-fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
-  version "1.0.11"
-  resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
-  integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
-  dependencies:
-    graceful-fs "^4.1.2"
-    inherits "~2.0.0"
-    mkdirp ">=0.5 0"
-    rimraf "2"
+fsevents@~2.1.2:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+  integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
 
-function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
+function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
-function.prototype.name@^1.0.0, function.prototype.name@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
-  integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==
+function.prototype.name@^1.0.0, function.prototype.name@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45"
+  integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==
   dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.1"
-    is-callable "^1.1.3"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
+    functions-have-names "^1.2.0"
 
-gauge@~2.7.3:
-  version "2.7.4"
-  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
-  integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
-  dependencies:
-    aproba "^1.0.3"
-    console-control-strings "^1.0.0"
-    has-unicode "^2.0.0"
-    object-assign "^4.1.0"
-    signal-exit "^3.0.0"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-    wide-align "^1.1.0"
+functions-have-names@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91"
+  integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==
 
 gen-css-identifier@^1.0.0:
   version "1.0.0"
@@ -6110,9 +5979,11 @@ gen-css-identifier@^1.0.0:
   integrity sha1-tEi11/lJzea9k/vI/BFNq0Byo0I=
 
 generate-function@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
-  integrity sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
+  integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==
+  dependencies:
+    is-property "^1.0.2"
 
 generate-object-property@^1.1.0:
   version "1.2.0"
@@ -6121,10 +5992,20 @@ generate-object-property@^1.1.0:
   dependencies:
     is-property "^1.0.0"
 
+gensync@^1.0.0-beta.1:
+  version "1.0.0-beta.1"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
+  integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+
 get-caller-file@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-  integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+  integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
 get-comments@^1.0.1:
   version "1.0.1"
@@ -6181,12 +6062,12 @@ gettext-parser@4.0.0-alpha.0:
     safe-buffer "^5.1.2"
 
 git-up@^2.0.0:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.10.tgz#20fe6bafbef4384cae253dc4f463c49a0c3bd2ec"
-  integrity sha512-2v4UN3qV2RGypD9QpmUjpk+4+RlYpW8GFuiZqQnKmvei08HsFPd0RfbDvEhnE4wBvnYs8ORVtYpOFuuCEmBVBw==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.1.0.tgz#2f14cfe78327e7c4a2b92fcac7bfc674fdfad40c"
+  integrity sha512-MJgwfcSd9qxgDyEYpRU/CDxNpUadrK80JHuEQDG4Urn0m7tpSOgCBrtiSIa9S9KH8Tbuo/TN8SSQmJBvsw1HkA==
   dependencies:
     is-ssh "^1.3.0"
-    parse-url "^1.3.0"
+    parse-url "^3.0.2"
 
 git-url-parse@^6.0.1:
   version "6.2.2"
@@ -6195,20 +6076,27 @@ git-url-parse@^6.0.1:
   dependencies:
     git-up "^2.0.0"
 
-github-slugger@1.1.3, github-slugger@^1.0.0, github-slugger@^1.1.1:
+github-slugger@1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.1.3.tgz#314a6e759a18c2b0cc5760d512ccbab549c549a7"
   integrity sha1-MUpudZoYwrDMV2DVEsy6tUnFSac=
   dependencies:
     emoji-regex ">=6.0.0 <=6.1.1"
 
+github-slugger@^1.0.0, github-slugger@^1.1.1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9"
+  integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==
+  dependencies:
+    emoji-regex ">=6.0.0 <=6.1.1"
+
 glob-all@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.1.0.tgz#8913ddfb5ee1ac7812656241b03d5217c64b02ab"
-  integrity sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.2.1.tgz#082ca81afd2247cbd3ed2149bb2630f4dc877d95"
+  integrity sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw==
   dependencies:
-    glob "^7.0.5"
-    yargs "~1.2.6"
+    glob "^7.1.2"
+    yargs "^15.3.1"
 
 glob-base@^0.3.0:
   version "0.3.0"
@@ -6233,6 +6121,13 @@ glob-parent@^3.0.0, glob-parent@^3.1.0:
     is-glob "^3.1.0"
     path-dirname "^1.0.0"
 
+glob-parent@~5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
+  dependencies:
+    is-glob "^4.0.1"
+
 glob-stream@^5.3.2:
   version "5.3.5"
   resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22"
@@ -6258,19 +6153,7 @@ glob@^5.0.15, glob@^5.0.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
-  integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-glob@^7.1.3:
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -6290,17 +6173,17 @@ global-dirs@^0.1.0:
     ini "^1.3.4"
 
 global@^4.3.0:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
-  integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
+  integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
   dependencies:
     min-document "^2.19.0"
-    process "~0.5.1"
+    process "^0.11.10"
 
 globals-docs@^2.3.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/globals-docs/-/globals-docs-2.4.0.tgz#f2c647544eb6161c7c38452808e16e693c2dafbb"
-  integrity sha512-B69mWcqCmT3jNYmSxRxxOXWfzu3Go8NQXPfl2o0qPd1EEFhwW0dFUg9ztTu915zPQzqwIhWAlw6hmfIcCK4kkQ==
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/globals-docs/-/globals-docs-2.4.1.tgz#d16887709f4a15eb22d97e96343591f87a2ee3db"
+  integrity sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==
 
 globals@^11.1.0, globals@^11.12.0:
   version "11.12.0"
@@ -6312,18 +6195,6 @@ globals@^9.14.0, globals@^9.18.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
   integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
 
-globby@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
-  integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=
-  dependencies:
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
 globby@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@@ -6345,9 +6216,9 @@ gm@~1.21.1:
     debug "~2.2.0"
 
 graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
-  version "4.1.11"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
-  integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
+  version "4.2.4"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+  integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
 
 "graceful-readlink@>= 1.0.0":
   version "1.0.1"
@@ -6378,26 +6249,22 @@ gulp-sourcemaps@1.6.0:
     through2 "^2.0.0"
     vinyl "^1.0.0"
 
-handle-thing@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
-  integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=
+handle-thing@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
+  integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
 
 handlebars@^4.0.3:
-  version "4.0.11"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
-  integrity sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=
+  version "4.7.6"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
+  integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
   dependencies:
-    async "^1.4.0"
-    optimist "^0.6.1"
-    source-map "^0.4.4"
+    minimist "^1.2.5"
+    neo-async "^2.6.0"
+    source-map "^0.6.1"
+    wordwrap "^1.0.0"
   optionalDependencies:
-    uglify-js "^2.6"
-
-har-schema@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
-  integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=
+    uglify-js "^3.1.4"
 
 har-schema@^2.0.0:
   version "2.0.0"
@@ -6414,23 +6281,7 @@ har-validator@~2.0.6:
     is-my-json-valid "^2.12.4"
     pinkie-promise "^2.0.0"
 
-har-validator@~4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
-  integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio=
-  dependencies:
-    ajv "^4.9.1"
-    har-schema "^1.0.5"
-
-har-validator@~5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
-  integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=
-  dependencies:
-    ajv "^5.1.0"
-    har-schema "^2.0.0"
-
-har-validator@~5.1.0:
+har-validator@~5.1.0, har-validator@~5.1.3:
   version "5.1.5"
   resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
   integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
@@ -6438,13 +6289,6 @@ har-validator@~5.1.0:
     ajv "^6.12.3"
     har-schema "^2.0.0"
 
-has-ansi@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e"
-  integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=
-  dependencies:
-    ansi-regex "^0.2.0"
-
 has-ansi@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -6477,21 +6321,11 @@ has-flag@^4.0.0:
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-has-symbols@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
-  integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
-
 has-symbols@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
   integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
 
-has-unicode@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
-
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -6530,38 +6364,32 @@ has@^1.0.1, has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-hash-base@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1"
-  integrity sha1-ZuodhW206KVHDK32/OI65SRO8uE=
-  dependencies:
-    inherits "^2.0.1"
-
 hash-base@^3.0.0:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
-  integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
+  integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
   dependencies:
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
+    inherits "^2.0.4"
+    readable-stream "^3.6.0"
+    safe-buffer "^5.2.0"
 
 hash.js@^1.0.0, hash.js@^1.0.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
-  integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
+  integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
   dependencies:
     inherits "^2.0.3"
-    minimalistic-assert "^1.0.0"
+    minimalistic-assert "^1.0.1"
 
 hast-util-is-element@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.0.tgz#3f7216978b2ae14d98749878782675f33be3ce00"
-  integrity sha1-P3IWl4sq4U2YdJh4eCZ18zvjzgA=
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425"
+  integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==
 
 hast-util-sanitize@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-1.1.2.tgz#d10bd6757a21e59c13abc8ae3530dd3b6d7d679e"
-  integrity sha1-0QvWdXoh5ZwTq8iuNTDdO219Z54=
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-1.3.1.tgz#4e60d66336bd67e52354d581967467029a933f2e"
+  integrity sha512-AIeKHuHx0Wk45nSkGVa2/ujQYTksnDl8gmmKo/mwQi7ag7IBZ8cM3nJ2G86SajbjGP/HRpud6kMkPtcM2i0Tlw==
   dependencies:
     xtend "^4.0.1"
 
@@ -6583,11 +6411,11 @@ hast-util-to-html@^3.0.0:
     xtend "^4.0.1"
 
 hast-util-whitespace@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.0.tgz#bd096919625d2936e1ff17bc4df7fd727f17ece9"
-  integrity sha1-vQlpGWJdKTbh/xe8Tff9cn8X7Ok=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41"
+  integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==
 
-hawk@3.1.3, hawk@~3.1.3:
+hawk@~3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
   integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=
@@ -6597,25 +6425,15 @@ hawk@3.1.3, hawk@~3.1.3:
     hoek "2.x.x"
     sntp "1.x.x"
 
-hawk@~6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
-  integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==
-  dependencies:
-    boom "4.x.x"
-    cryptiles "3.x.x"
-    hoek "4.x.x"
-    sntp "2.x.x"
-
-he@1.1.x:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
-  integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
+he@1.2.x:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
 highlight.js@^9.1.0:
-  version "9.12.0"
-  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
-  integrity sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=
+  version "9.18.3"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634"
+  integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
 
 history@3, history@^3.0.0:
   version "3.3.0"
@@ -6641,20 +6459,22 @@ hoek@2.x.x:
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
   integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
 
-hoek@4.x.x:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
-  integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==
-
-hoist-non-react-statics@1.2.0, hoist-non-react-statics@^1.0.5, hoist-non-react-statics@^1.2.0:
+hoist-non-react-statics@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
   integrity sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=
 
-hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
-  integrity sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==
+hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
+  version "2.5.5"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
+  integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
+
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+  integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+  dependencies:
+    react-is "^16.7.0"
 
 home-or-tmp@^2.0.0:
   version "2.0.0"
@@ -6665,9 +6485,9 @@ home-or-tmp@^2.0.0:
     os-tmpdir "^1.0.1"
 
 hosted-git-info@^2.1.4:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222"
-  integrity sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+  integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
 
 hpack.js@^2.1.6:
   version "2.1.6"
@@ -6680,9 +6500,16 @@ hpack.js@^2.1.6:
     wbuf "^1.1.0"
 
 html-comment-regex@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
-  integrity sha1-ZouTd26q5V696POtRkswekljYl4=
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
+  integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
+
+html-element-map@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22"
+  integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==
+  dependencies:
+    array-filter "^1.0.0"
 
 html-encoding-sniffer@^1.0.1:
   version "1.0.2"
@@ -6692,23 +6519,22 @@ html-encoding-sniffer@^1.0.1:
     whatwg-encoding "^1.0.1"
 
 html-entities@^1.2.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
-  integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44"
+  integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==
 
 html-minifier@^3.2.3:
-  version "3.5.8"
-  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.8.tgz#5ccdb1f73a0d654e6090147511f6e6b2ee312700"
-  integrity sha512-WX7D6PB9PFq05fZ1/CyxPUuyqXed6vh2fGOM80+zJT5wAO93D/cUjLs0CcbBFjQmlwmCgRvl97RurtArIpOnkw==
+  version "3.5.21"
+  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c"
+  integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==
   dependencies:
     camel-case "3.0.x"
-    clean-css "4.1.x"
-    commander "2.12.x"
-    he "1.1.x"
-    ncname "1.0.x"
+    clean-css "4.2.x"
+    commander "2.17.x"
+    he "1.2.x"
     param-case "2.1.x"
     relateurl "0.2.x"
-    uglify-js "3.3.x"
+    uglify-js "3.4.x"
 
 html-tags@^2.0.0:
   version "2.0.0"
@@ -6716,9 +6542,9 @@ html-tags@^2.0.0:
   integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
 
 html-void-elements@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.2.tgz#9d22e0ca32acc95b3f45b8d5b4f6fbdc05affd55"
-  integrity sha1-nSLgyjKsyVs/RbjVtPb73AWv/VU=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483"
+  integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==
 
 html-webpack-harddisk-plugin@^0.1.0:
   version "0.1.0"
@@ -6739,65 +6565,78 @@ html-webpack-plugin@^2.30.1:
     pretty-error "^2.0.2"
     toposort "^1.0.0"
 
-htmlparser2@^3.9.1:
-  version "3.9.2"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
-  integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=
+htmlparser2@^3.3.0, htmlparser2@^3.9.1:
+  version "3.10.1"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
+  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
   dependencies:
-    domelementtype "^1.3.0"
+    domelementtype "^1.3.1"
     domhandler "^2.3.0"
     domutils "^1.5.1"
     entities "^1.1.1"
     inherits "^2.0.1"
-    readable-stream "^2.0.2"
-
-htmlparser2@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
-  integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=
-  dependencies:
-    domelementtype "1"
-    domhandler "2.1"
-    domutils "1.1"
-    readable-stream "1.0"
+    readable-stream "^3.1.1"
 
 http-deceiver@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
   integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
 
-http-errors@1.6.2, http-errors@~1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
-  integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=
+http-errors@1.7.2:
+  version "1.7.2"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
+  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
   dependencies:
-    depd "1.1.1"
+    depd "~1.1.2"
     inherits "2.0.3"
-    setprototypeof "1.0.3"
-    statuses ">= 1.3.1 < 2"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
 
-http-parser-js@>=0.4.0:
-  version "0.4.9"
-  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1"
-  integrity sha1-6hoE+2St/wJC6ZdPKX3Uw8rSceE=
+http-errors@~1.6.2:
+  version "1.6.3"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+  integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.3"
+    setprototypeof "1.1.0"
+    statuses ">= 1.4.0 < 2"
 
-http-proxy-middleware@~0.17.4:
-  version "0.17.4"
-  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
-  integrity sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=
+http-errors@~1.7.2:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
   dependencies:
-    http-proxy "^1.16.2"
-    is-glob "^3.1.0"
-    lodash "^4.17.2"
-    micromatch "^2.3.11"
+    depd "~1.1.2"
+    inherits "2.0.4"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
+
+http-parser-js@>=0.5.1:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77"
+  integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==
+
+http-proxy-middleware@^0.19.1:
+  version "0.19.2"
+  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz#ee73dcc8348165afefe8de2ff717751d181608ee"
+  integrity sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==
+  dependencies:
+    http-proxy "^1.18.1"
+    is-glob "^4.0.0"
+    lodash "^4.17.11"
+    micromatch "^3.1.10"
 
-http-proxy@^1.16.2:
-  version "1.16.2"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
-  integrity sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=
+http-proxy@^1.18.1:
+  version "1.18.1"
+  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+  integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
   dependencies:
-    eventemitter3 "1.x.x"
-    requires-port "1.x.x"
+    eventemitter3 "^4.0.0"
+    follow-redirects "^1.0.0"
+    requires-port "^1.0.0"
 
 http-signature@~1.1.0:
   version "1.1.1"
@@ -6822,7 +6661,7 @@ https-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
   integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
 
-https-proxy-agent@^1.0.0, https-proxy-agent@~1.0.0:
+https-proxy-agent@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6"
   integrity sha1-NffabEjOTdv6JkiRrFk+5f+GceY=
@@ -6831,6 +6670,14 @@ https-proxy-agent@^1.0.0, https-proxy-agent@~1.0.0:
     debug "2"
     extend "3"
 
+https-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
+  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
+  dependencies:
+    agent-base "6"
+    debug "4"
+
 humanize-plus@^1.8.1:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
@@ -6841,18 +6688,20 @@ icepick@2.3.0:
   resolved "https://registry.yarnpkg.com/icepick/-/icepick-2.3.0.tgz#1d1b0b28b80c1ff720a1f62359dab46392806f5c"
   integrity sha512-1l106azHB9v3J9S4x5wYJyk+34rmFh2uptHyH8SCrYTy9f0qFAdMtUeOQTsBYtF+TuKQ0jdWpxClLec/7H5BjQ==
 
-iconv-lite@0.4.19:
-  version "0.4.19"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
-  integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
-
-iconv-lite@^0.4.5, iconv-lite@~0.4.13:
+iconv-lite@0.4.24, iconv-lite@^0.4.5:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+iconv-lite@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
+  integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3.0.0"
+
 icss-replace-symbols@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -6866,9 +6715,9 @@ icss-utils@^2.1.0:
     postcss "^6.0.1"
 
 ieee754@^1.1.4:
-  version "1.1.8"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
-  integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
 
 iferr@^0.1.5:
   version "0.1.5"
@@ -6876,14 +6725,14 @@ iferr@^0.1.5:
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
 iframe-resizer@^3.5.11:
-  version "3.5.15"
-  resolved "https://registry.yarnpkg.com/iframe-resizer/-/iframe-resizer-3.5.15.tgz#67ae959cc07478cdfb71347ead8ebfa95bd9824a"
-  integrity sha512-ZdmPmqafz5Vy2ooUW38LTl6ACcArfK53FQPtR8gOwb74nXiMRkuOV4Vr4w1LHu/YDVlnB1D7qONVX6BUD5DbHA==
+  version "3.6.6"
+  resolved "https://registry.yarnpkg.com/iframe-resizer/-/iframe-resizer-3.6.6.tgz#ece6a0b7e879cfe3783a1003261408152ff9bd9b"
+  integrity sha512-WYprloB8s18Be9wbybQZmeEWueEczJH2is/5DxDn4jH8Bs2DuaSuaOeYICe6IvmF04SBy4mQ+pX4Xc+E9FEMSg==
 
 ignore@^3.2.0:
-  version "3.3.7"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
-  integrity sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==
+  version "3.3.10"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
+  integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
 
 image-diff@^1.6.3:
   version "1.6.3"
@@ -6902,6 +6751,21 @@ image-exists@^1.1.0:
   resolved "https://registry.yarnpkg.com/image-exists/-/image-exists-1.1.0.tgz#ba49cccbaddca8cbbf10f89cafd4d1c8ecfd38d0"
   integrity sha1-uknMy63cqMu/EPicr9TRyOz9ONA=
 
+import-cwd@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
+  integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
+  dependencies:
+    import-from "^2.1.0"
+
+import-fresh@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
+  integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
+  dependencies:
+    caller-path "^2.0.0"
+    resolve-from "^3.0.0"
+
 import-fresh@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
@@ -6910,6 +6774,13 @@ import-fresh@^3.1.0:
     parent-module "^1.0.0"
     resolve-from "^4.0.0"
 
+import-from@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
+  integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
+  dependencies:
+    resolve-from "^3.0.0"
+
 import-local@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
@@ -6953,11 +6824,6 @@ indexes-of@^1.0.1:
   resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
   integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
 
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
-
 inflection@^1.7.1:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
@@ -6971,7 +6837,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -6986,7 +6852,7 @@ inherits@2.0.3:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
-ini@^1.3.3, ini@^1.3.4, ini@~1.3.0:
+ini@^1.3.3, ini@^1.3.4:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -7028,11 +6894,11 @@ internal-ip@1.2.0:
     meow "^3.3.0"
 
 interpret@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-  integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
-invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
   integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@@ -7049,10 +6915,10 @@ ip@^1.1.0, ip@^1.1.5:
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
   integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
 
-ipaddr.js@1.5.2:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
-  integrity sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=
+ipaddr.js@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
 is-absolute-url@^2.0.0:
   version "2.1.0"
@@ -7090,9 +6956,9 @@ is-accessor-descriptor@^1.0.0:
     kind-of "^6.0.0"
 
 is-alphabetical@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.3.tgz#eb04cc47219a8895d8450ace4715abff2258a1f8"
-  integrity sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
+  integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
 
 is-alphanumeric@^1.0.0:
   version "1.0.0"
@@ -7100,22 +6966,27 @@ is-alphanumeric@^1.0.0:
   integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=
 
 is-alphanumerical@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz#57ae21c374277b3defe0274c640a5704b8f6657c"
-  integrity sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
+  integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
   dependencies:
     is-alphabetical "^1.0.0"
     is-decimal "^1.0.0"
 
+is-arguments@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
+  integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
+
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
 is-arrayish@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd"
-  integrity sha1-wt/DhquqDD4zxI2z/ocFnmkGXv0=
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+  integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
 
 is-binary-path@^1.0.0:
   version "1.0.1"
@@ -7124,52 +6995,35 @@ is-binary-path@^1.0.0:
   dependencies:
     binary-extensions "^1.0.0"
 
-is-boolean-object@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
-  integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=
+is-binary-path@~2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
+is-boolean-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
+  integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
 
 is-buffer@^1.1.4, is-buffer@^1.1.5:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
-is-builtin-module@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
-  integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
-  dependencies:
-    builtin-modules "^1.0.0"
-
-is-callable@^1.1.3, is-callable@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
-  integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
-
-is-callable@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
-  integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
-
-is-callable@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
-  integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
+is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
+  integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
 
-is-ci@1.2.1:
+is-ci@1.2.1, is-ci@^1.0.9:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
   integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
   dependencies:
     ci-info "^1.5.0"
 
-is-ci@^1.0.9:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5"
-  integrity sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==
-  dependencies:
-    ci-info "^1.0.0"
-
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -7185,14 +7039,14 @@ is-data-descriptor@^1.0.0:
     kind-of "^6.0.0"
 
 is-date-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
-  integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
+  integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
 
 is-decimal@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.3.tgz#381068759b9dc807d8c0dc0bfbae2b68e1da48b7"
-  integrity sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
+  integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
 
 is-descriptor@^0.1.0:
   version "0.1.6"
@@ -7203,7 +7057,7 @@ is-descriptor@^0.1.0:
     is-data-descriptor "^0.1.4"
     kind-of "^5.0.0"
 
-is-descriptor@^1.0.0:
+is-descriptor@^1.0.0, is-descriptor@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
   integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
@@ -7252,11 +7106,9 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
 is-finite@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
-  integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
-  dependencies:
-    number-is-nan "^1.0.0"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
+  integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
 
 is-fullwidth-code-point@^1.0.0:
   version "1.0.0"
@@ -7270,6 +7122,11 @@ is-fullwidth-code-point@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
   integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
 
+is-fullwidth-code-point@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
 is-glob@^2.0.0, is-glob@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -7284,17 +7141,17 @@ is-glob@^3.1.0:
   dependencies:
     is-extglob "^2.1.0"
 
-is-glob@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
-  integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
 is-hexadecimal@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz#e8a426a69b6d31470d3a33a47bb825cda02506ee"
-  integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
+  integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
 
 is-installed-globally@0.1.0:
   version "0.1.0"
@@ -7304,20 +7161,31 @@ is-installed-globally@0.1.0:
     global-dirs "^0.1.0"
     is-path-inside "^1.0.0"
 
+is-my-ip-valid@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
+  integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==
+
 is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
-  integrity sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==
+  version "2.20.5"
+  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz#5eca6a8232a687f68869b7361be1612e7512e5df"
+  integrity sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==
   dependencies:
     generate-function "^2.0.0"
     generate-object-property "^1.1.0"
+    is-my-ip-valid "^1.0.0"
     jsonpointer "^4.0.0"
     xtend "^4.0.0"
 
-is-number-object@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799"
-  integrity sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=
+is-negative-zero@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461"
+  integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=
+
+is-number-object@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
+  integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
 
 is-number@^2.1.0:
   version "2.1.0"
@@ -7333,27 +7201,30 @@ is-number@^3.0.0:
   dependencies:
     kind-of "^3.0.2"
 
+is-number@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
+  integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
 is-obj@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
 
-is-odd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088"
-  integrity sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=
-  dependencies:
-    is-number "^3.0.0"
-
 is-path-cwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
   integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=
 
 is-path-in-cwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
-  integrity sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
+  integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==
   dependencies:
     is-path-inside "^1.0.0"
 
@@ -7387,33 +7258,19 @@ is-primitive@^2.0.0:
   integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
 
 is-promise@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-  integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
+  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
 
-is-property@^1.0.0:
+is-property@^1.0.0, is-property@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
   integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=
 
-is-regex@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
-  integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=
-  dependencies:
-    has "^1.0.1"
-
-is-regex@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
-  integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
-  dependencies:
-    has "^1.0.3"
-
-is-regex@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
-  integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
+is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
+  integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
   dependencies:
     has-symbols "^1.0.1"
 
@@ -7437,9 +7294,9 @@ is-relative@^1.0.0:
     is-unc-path "^1.0.0"
 
 is-resolvable@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.1.tgz#acca1cd36dbe44b974b924321555a70ba03b1cf4"
-  integrity sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
+  integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
 
 is-retina@^1.0.3:
   version "1.0.3"
@@ -7447,9 +7304,9 @@ is-retina@^1.0.3:
   integrity sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M=
 
 is-ssh@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.0.tgz#ebea1169a2614da392a63740366c3ce049d8dff6"
-  integrity sha1-6+oRaaJhTaOSpjdANmw84EnY3/Y=
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b"
+  integrity sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ==
   dependencies:
     protocols "^1.1.0"
 
@@ -7458,11 +7315,6 @@ is-stream@^1.0.1, is-stream@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
 
-is-string@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
-  integrity sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=
-
 is-string@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
@@ -7481,11 +7333,11 @@ is-svg@^2.0.0:
     html-comment-regex "^1.1.0"
 
 is-symbol@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
-  integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
   dependencies:
-    has-symbols "^1.0.0"
+    has-symbols "^1.0.1"
 
 is-typedarray@~1.0.0:
   version "1.0.0"
@@ -7517,24 +7369,24 @@ is-valid-glob@^0.3.0:
   integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=
 
 is-whitespace-character@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz#b3ad9546d916d7d3ffa78204bca0c26b56257fac"
-  integrity sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7"
+  integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==
 
 is-windows@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
   integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw=
 
-is-windows@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9"
-  integrity sha1-MQ23D3QtJZoWo2kgK1GvhCMzENk=
+is-windows@^1.0.1, is-windows@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
+  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
 is-word-character@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.3.tgz#264d15541cbad0ba833d3992c34e6b40873b08aa"
-  integrity sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230"
+  integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==
 
 is-wsl@^1.1.0:
   version "1.1.0"
@@ -7592,79 +7444,79 @@ isstream@0.1.x, isstream@~0.1.2:
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
 istanbul-api@^1.1.0-alpha.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620"
-  integrity sha512-oFCwXvd65amgaPCzqrR+a2XjanS1MvpXN6l/MlMUTv6uiA1NOgGX+I0uyq8Lg3GDxsxPsaP1049krz3hIJ5+KA==
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
+  integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==
   dependencies:
     async "^2.1.4"
     fileset "^2.0.2"
-    istanbul-lib-coverage "^1.1.1"
-    istanbul-lib-hook "^1.1.0"
-    istanbul-lib-instrument "^1.9.1"
-    istanbul-lib-report "^1.1.2"
-    istanbul-lib-source-maps "^1.2.2"
-    istanbul-reports "^1.1.3"
+    istanbul-lib-coverage "^1.2.1"
+    istanbul-lib-hook "^1.2.2"
+    istanbul-lib-instrument "^1.10.2"
+    istanbul-lib-report "^1.1.5"
+    istanbul-lib-source-maps "^1.2.6"
+    istanbul-reports "^1.5.1"
     js-yaml "^3.7.0"
     mkdirp "^0.5.1"
     once "^1.4.0"
 
-istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
-  integrity sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==
+istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
+  integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==
 
-istanbul-lib-hook@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b"
-  integrity sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==
+istanbul-lib-hook@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
+  integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==
   dependencies:
     append-transform "^0.4.0"
 
-istanbul-lib-instrument@^1.1.1, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
-  integrity sha512-RQmXeQ7sphar7k7O1wTNzVczF9igKpaeGQAG9qR2L+BS4DCJNTI9nytRmIVYevwO0bbq+2CXvJmYDuz0gMrywA==
+istanbul-lib-instrument@^1.1.1, istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2:
+  version "1.10.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
+  integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==
   dependencies:
     babel-generator "^6.18.0"
     babel-template "^6.16.0"
     babel-traverse "^6.18.0"
     babel-types "^6.18.0"
     babylon "^6.18.0"
-    istanbul-lib-coverage "^1.1.1"
+    istanbul-lib-coverage "^1.2.1"
     semver "^5.3.0"
 
-istanbul-lib-report@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425"
-  integrity sha512-UTv4VGx+HZivJQwAo1wnRwe1KTvFpfi/NYwN7DcsrdzMXwpRT/Yb6r4SBPoHWj4VuQPakR32g4PUUeyKkdDkBA==
+istanbul-lib-report@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
+  integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==
   dependencies:
-    istanbul-lib-coverage "^1.1.1"
+    istanbul-lib-coverage "^1.2.1"
     mkdirp "^0.5.1"
     path-parse "^1.0.5"
     supports-color "^3.1.2"
 
-istanbul-lib-source-maps@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c"
-  integrity sha512-8BfdqSfEdtip7/wo1RnrvLpHVEd8zMZEDmOFEnpC6dg0vXflHt9nvoAyQUzig2uMSXfF2OBEYBV3CVjIL9JvaQ==
+istanbul-lib-source-maps@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
+  integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==
   dependencies:
     debug "^3.1.0"
-    istanbul-lib-coverage "^1.1.1"
+    istanbul-lib-coverage "^1.2.1"
     mkdirp "^0.5.1"
     rimraf "^2.6.1"
     source-map "^0.5.3"
 
-istanbul-reports@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10"
-  integrity sha512-ZEelkHh8hrZNI5xDaKwPMFwDsUf5wIEI2bXAFGp1e6deR2mnEKBPhLJEgr4ZBt8Gi6Mj38E/C8kcy9XLggVO2Q==
+istanbul-reports@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
+  integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==
   dependencies:
     handlebars "^4.0.3"
 
-jasmine-core@^2.4.1, jasmine-core@~2.8.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
-  integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=
+jasmine-core@^2.4.1, jasmine-core@~2.99.0:
+  version "2.99.1"
+  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.99.1.tgz#e6400df1e6b56e130b61c4bcd093daa7f6e8ca15"
+  integrity sha1-5kAN8ea1bhMLYcS80JPap/boyhU=
 
 jasmine-promises@^0.4.1:
   version "0.4.1"
@@ -7672,9 +7524,9 @@ jasmine-promises@^0.4.1:
   integrity sha1-OGtj4scU0z2bG3ra5Qd3M2bb8Ks=
 
 jasmine-reporters@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.3.0.tgz#eb8cb7359658572a87eef4aa088a363036f3792a"
-  integrity sha1-64y3NZZYVyqH7vSqCIo2MDbzeSo=
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz#898818ffc234eb8b3f635d693de4586f95548d43"
+  integrity sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==
   dependencies:
     mkdirp "^0.5.1"
     xmldom "^0.1.22"
@@ -7687,13 +7539,13 @@ jasmine-spec-reporter@^3.0.0:
     colors "1.1.2"
 
 jasmine@^2.4.1:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e"
-  integrity sha1-awicChFXax8W3xG4AUbZHU6Lij4=
+  version "2.99.0"
+  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.99.0.tgz#8ca72d102e639b867c6489856e0e18a9c7aa42b7"
+  integrity sha1-jKctEC5jm4Z8ZImFbg4YqceqQrc=
   dependencies:
     exit "^0.1.2"
     glob "^7.0.6"
-    jasmine-core "~2.8.0"
+    jasmine-core "~2.99.0"
 
 jest-changed-files@^19.0.2:
   version "19.0.2"
@@ -7757,15 +7609,15 @@ jest-diff@^19.0.0:
     jest-matcher-utils "^19.0.0"
     pretty-format "^19.0.0"
 
-jest-diff@^24.0.0, jest-diff@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
-  integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
+jest-diff@^24.0.0, jest-diff@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
+  integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
   dependencies:
     chalk "^2.0.1"
-    diff-sequences "^24.3.0"
-    jest-get-type "^24.8.0"
-    pretty-format "^24.8.0"
+    diff-sequences "^24.9.0"
+    jest-get-type "^24.9.0"
+    pretty-format "^24.9.0"
 
 jest-environment-jsdom@^19.0.2:
   version "19.0.2"
@@ -7789,10 +7641,10 @@ jest-file-exists@^19.0.0:
   resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8"
   integrity sha1-zKLlh6EeyS4kz+qz+KlNZX8/zrg=
 
-jest-get-type@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
-  integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
+jest-get-type@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
+  integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
 
 jest-haste-map@^19.0.0:
   version "19.0.2"
@@ -7817,9 +7669,9 @@ jest-jasmine2@^19.0.2:
     jest-snapshot "^19.0.2"
 
 jest-localstorage-mock@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.3.0.tgz#86eda98d5af3aed687aebf11dbab3ca0f8fe66ff"
-  integrity sha512-Lk+awEPuIz0PSERHtnsXyMVLvf/4mZ3sZBEjKG5sJHvey2/i2JfQmmb/NHhialMbHXZILBORzuH64YXhWGlLsQ==
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.3.tgz#29a77a892acdbe2303436a5eb28d03997d98a430"
+  integrity sha512-UgifkHKoWVRUoSqO4Z4Z+Hl1NbiYBVDlmkmulFFeRRneGECWAlAdGWJdyz+2NisjOZnnQoxQl0s5dQ7ch62Jxw==
 
 jest-matcher-utils@^19.0.0:
   version "19.0.0"
@@ -7830,14 +7682,14 @@ jest-matcher-utils@^19.0.0:
     pretty-format "^19.0.0"
 
 jest-matcher-utils@^24.0.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495"
-  integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
+  integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
   dependencies:
     chalk "^2.0.1"
-    jest-diff "^24.8.0"
-    jest-get-type "^24.8.0"
-    pretty-format "^24.8.0"
+    jest-diff "^24.9.0"
+    jest-get-type "^24.9.0"
+    pretty-format "^24.9.0"
 
 jest-matchers@^19.0.0:
   version "19.0.0"
@@ -7959,9 +7811,9 @@ joi@^6.10.1:
     topo "1.x.x"
 
 js-base64@^2.1.9:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa"
-  integrity sha512-Wehd+7Pf9tFvGb+ydPm9TjYjV8X1YHOVyG8QyELZxEMqOhemVwGRmoG8iQ/soqI3n8v4xn59zaLxiCJiaaRzKA==
+  version "2.6.4"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
+  integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
 
 js-base64@~2.1.8:
   version "2.1.9"
@@ -7969,14 +7821,9 @@ js-base64@~2.1.8:
   integrity sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=
 
 js-cookie@^2.1.2:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
-  integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=
-
-js-levenshtein@^1.1.3:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
-  integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
+  integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
 
 js-managed-css@^1.4.1:
   version "1.4.2"
@@ -7986,20 +7833,20 @@ js-managed-css@^1.4.1:
     gen-css-identifier "^1.0.0"
     insert-css "^0.2.0"
 
-js-tokens@^3.0.0, js-tokens@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-  integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 
-js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.8.4:
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
-  integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
+js-tokens@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+  integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
+
+js-yaml@^3.13.1, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.8.4:
+  version "3.14.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
+  integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
@@ -8088,6 +7935,11 @@ json-parse-better-errors@^1.0.1:
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
   integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
+json-parse-even-better-errors@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
 json-schema-traverse@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
@@ -8103,6 +7955,11 @@ json-schema@0.2.3:
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
   integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
 
+json-stable-stringify-without-jsonify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+  integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+
 json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
@@ -8116,9 +7973,9 @@ json-stringify-safe@~5.0.1:
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
 json3@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
-  integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
+  integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
 
 json5@^0.5.0, json5@^0.5.1:
   version "0.5.1"
@@ -8132,12 +7989,12 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
-  integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
+json5@^2.1.2:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+  integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
   dependencies:
-    minimist "^1.2.0"
+    minimist "^1.2.5"
 
 jsonfile@^2.1.0:
   version "2.4.0"
@@ -8164,9 +8021,9 @@ jsonparse@^1.2.0:
   integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
 
 jsonpointer@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
-  integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc"
+  integrity sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==
 
 jsonwebtoken@^7.2.1:
   version "7.4.3"
@@ -8199,23 +8056,26 @@ jsx-ast-utils@^1.3.4:
   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
   integrity sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=
 
-jwa@^1.1.4:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
-  integrity sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=
+just-curry-it@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5"
+  integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg==
+
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
   dependencies:
-    base64url "2.0.0"
     buffer-equal-constant-time "1.0.1"
-    ecdsa-sig-formatter "1.0.9"
+    ecdsa-sig-formatter "1.0.11"
     safe-buffer "^5.0.1"
 
 jws@^3.1.4:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
-  integrity sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
   dependencies:
-    base64url "^2.0.0"
-    jwa "^1.1.4"
+    jwa "^1.4.1"
     safe-buffer "^5.0.1"
 
 kebab-case@^1.0.0:
@@ -8224,9 +8084,9 @@ kebab-case@^1.0.0:
   integrity sha1-P55JkK3K0MaGwOcB92RYaPdfkes=
 
 killable@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b"
-  integrity sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
+  integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==
 
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
@@ -8242,15 +8102,15 @@ kind-of@^4.0.0:
   dependencies:
     is-buffer "^1.1.5"
 
-kind-of@^5.0.0, kind-of@^5.0.2:
+kind-of@^5.0.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
   integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
 
 kind-of@^6.0.0, kind-of@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
-  integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+  integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
 lazy-ass@1.6.0:
   version "1.6.0"
@@ -8262,13 +8122,6 @@ lazy-cache@^1.0.3:
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
   integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4=
 
-lazy-cache@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
-  integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
-  dependencies:
-    set-getter "^0.1.0"
-
 lazystream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
@@ -8284,9 +8137,9 @@ lcid@^1.0.0:
     invert-kv "^1.0.0"
 
 leaflet-draw@^0.4.9:
-  version "0.4.13"
-  resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-0.4.13.tgz#b9467d8d6523edc5912e93727951005b77b62895"
-  integrity sha512-N/RFhS08viXfJJsGSuM2ZEWcez0F70iWHoHawMWhZ7GDjHyhyId+RLjbb7TxwK/aAbsf7tqz0IP6f1hr0Z1wpw==
+  version "0.4.14"
+  resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-0.4.14.tgz#1b5b06d570873a015aa96b80d664dab496c45a4a"
+  integrity sha512-O99KSPjyHNMDE+uD/fpdPydvQfTE8QruaG7ylEEtKCIaSSb60mCWoDdGUqGEHU9PGPu2JxbEfkGTXY9eYv7aEw==
 
 leaflet.heat@^0.2.0:
   version "0.2.0"
@@ -8294,9 +8147,9 @@ leaflet.heat@^0.2.0:
   integrity sha1-EJ2M9Ybwre5B8Fr/Ax4np3/swik=
 
 leaflet@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.2.0.tgz#fd5d93d9cb00091f5f8a69206d0d6744c1c82697"
-  integrity sha512-Bold8phAE6WcRsuwhofrQ7cOK1REFHaYIkKuj7+TBYK3ONKRpGGIb5oXR5akYotFnrWN0TWKh6Svlhflm3dogg==
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
+  integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
 
 leven@^2.0.0:
   version "2.1.0"
@@ -8382,10 +8235,10 @@ listr@0.12.0, listr@^0.12.0:
     stream-to-observable "^0.1.0"
     strip-ansi "^3.0.1"
 
-livereload-js@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2"
-  integrity sha1-bIclfmSKtHW8JOoldFftzB+NC8I=
+livereload-js@^2.3.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c"
+  integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==
 
 load-json-file@^1.0.0:
   version "1.1.0"
@@ -8408,18 +8261,28 @@ load-json-file@^2.0.0:
     pify "^2.0.0"
     strip-bom "^3.0.0"
 
+load-json-file@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
+  integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^4.0.0"
+    pify "^3.0.0"
+    strip-bom "^3.0.0"
+
 loader-fs-cache@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc"
-  integrity sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9"
+  integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==
   dependencies:
     find-cache-dir "^0.1.1"
-    mkdirp "0.5.1"
+    mkdirp "^0.5.1"
 
 loader-runner@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
-  integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
+  integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
 loader-utils@^0.2.16:
   version "0.2.17"
@@ -8431,14 +8294,14 @@ loader-utils@^0.2.16:
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@^1.0.2, loader-utils@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
-  integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
+loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
+  integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
   dependencies:
-    big.js "^3.1.3"
-    emojis-list "^2.0.0"
-    json5 "^0.5.0"
+    big.js "^5.2.2"
+    emojis-list "^3.0.0"
+    json5 "^1.0.1"
 
 locate-path@^2.0.0:
   version "2.0.0"
@@ -8456,10 +8319,17 @@ locate-path@^3.0.0:
     p-locate "^3.0.0"
     path-exists "^3.0.0"
 
-lodash-es@^4.17.4, lodash-es@^4.2.0, lodash-es@^4.2.1:
-  version "4.17.4"
-  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
-  integrity sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=
+locate-path@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+  dependencies:
+    p-locate "^4.1.0"
+
+lodash-es@^4.2.1:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
+  integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
 
 lodash._basefor@^3.0.0:
   version "3.0.3"
@@ -8471,7 +8341,7 @@ lodash._baseget@^3.0.0:
   resolved "https://registry.yarnpkg.com/lodash._baseget/-/lodash._baseget-3.7.2.tgz#1b6ae1d5facf3c25532350a13c1197cb8bb674f4"
   integrity sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=
 
-lodash._reinterpolate@~3.0.0:
+lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
   integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
@@ -8589,9 +8459,9 @@ lodash.memoize@^4.1.2:
   integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
 
 lodash.merge@^4.4.0:
-  version "4.6.1"
-  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
-  integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
 lodash.once@^4.0.0, lodash.once@^4.1.1:
   version "4.1.1"
@@ -8619,39 +8489,34 @@ lodash.some@^4.4.0:
   integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=
 
 lodash.template@^4.2.4:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
-  integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
+  integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
   dependencies:
-    lodash._reinterpolate "~3.0.0"
+    lodash._reinterpolate "^3.0.0"
     lodash.templatesettings "^4.0.0"
 
 lodash.templatesettings@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
-  integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
+  integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
   dependencies:
-    lodash._reinterpolate "~3.0.0"
+    lodash._reinterpolate "^3.0.0"
 
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@4.17.15, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
+lodash@4.17.15:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
   integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
 
-lodash@^3.7.0:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-  integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
-
-lodash@^4.17.19:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
+  version "4.17.20"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+  integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
 
 log-symbols@2.2.0:
   version "2.2.0"
@@ -8676,28 +8541,21 @@ log-update@^1.0.2:
     cli-cursor "^1.0.2"
 
 loglevel@^1.4.1:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.0.tgz#ae0caa561111498c5ba13723d6fb631d24003934"
-  integrity sha1-rgyqVhERSYxboTcj1vtjHSQAOTQ=
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
+  integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
 
 longest-streak@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
-  integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
+  integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
 
 longest@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
   integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
 
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
-  integrity sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=
-  dependencies:
-    js-tokens "^3.0.0"
-
-loose-envify@^1.3.1, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -8723,22 +8581,22 @@ lru-cache@^2.6.5:
   integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=
 
 lru-cache@^4.0.1, lru-cache@^4.1.1:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f"
-  integrity sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+  integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
   dependencies:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
 
-macaddress@^0.2.8:
-  version "0.2.8"
-  resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
-  integrity sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=
+lz-string@^1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
+  integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
 
 make-dir@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51"
-  integrity sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
+  integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
   dependencies:
     pify "^3.0.0"
 
@@ -8775,14 +8633,14 @@ map-visit@^1.0.0:
     object-visit "^1.0.0"
 
 markdown-escapes@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.3.tgz#6155e10416efaafab665d466ce598216375195f5"
-  integrity sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
+  integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
 
 markdown-table@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.1.tgz#4b3dd3a133d1518b8ef0dbc709bf2a1b4824bc8c"
-  integrity sha1-Sz3ToTPRUYuO8NvHCb8qG0gkvIw=
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60"
+  integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
 
 material-colors@^1.2.1:
   version "1.2.6"
@@ -8790,17 +8648,23 @@ material-colors@^1.2.1:
   integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
 
 math-expression-evaluator@^1.2.14:
-  version "1.2.17"
-  resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
-  integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw=
+  version "1.2.22"
+  resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz#c14dcb3d8b4d150e5dcea9c68c8dad80309b0d5e"
+  integrity sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==
+
+math-random@^1.0.1:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
+  integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
 
 md5.js@^1.3.4:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d"
-  integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0=
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
+  integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
   dependencies:
     hash-base "^3.0.0"
     inherits "^2.0.1"
+    safe-buffer "^5.1.2"
 
 mdast-add-list-metadata@1.0.1:
   version "1.0.1"
@@ -8810,17 +8674,16 @@ mdast-add-list-metadata@1.0.1:
     unist-util-visit-parents "1.1.2"
 
 mdast-util-compact@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.1.tgz#cdb5f84e2b6a2d3114df33bd05d9cb32e3c4083a"
-  integrity sha1-zbX4TitqLTEU3zO9BdnLMuPECDo=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593"
+  integrity sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==
   dependencies:
-    unist-util-modify-children "^1.0.0"
     unist-util-visit "^1.1.0"
 
 mdast-util-definitions@^1.2.0:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.2.tgz#673f4377c3e23d3de7af7a4fe2214c0e221c5ac7"
-  integrity sha512-9NloPSwaB9f1PKcGqaScfqRf6zKOEjTIXVIbPOmgWI/JKxznlgVXC5C+8qgl3AjYg2vJBRgLYfLICaNiac89iA==
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.5.tgz#3fe622a4171c774ebd06f11e9f8af7ec53ea5c74"
+  integrity sha512-CJXEdoLfiISCDc2JB6QLb79pYfI6+GcIH+W2ox9nMc7od0Pz+bovcHsiq29xAQY6ayqe/9CsK2VzkSJdg1pFYA==
   dependencies:
     unist-util-visit "^1.0.0"
 
@@ -8849,14 +8712,14 @@ mdast-util-to-hast@^2.1.1:
     xtend "^4.0.1"
 
 mdast-util-to-string@^1.0.0, mdast-util-to-string@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.4.tgz#5c455c878c9355f0c1e7f3e8b719cf583691acfb"
-  integrity sha1-XEVch4yTVfDB5/PotxnPWDaRrPs=
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527"
+  integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==
 
 mdast-util-toc@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-2.0.1.tgz#b1d2cb23bfb01f812fa7b55bffe8b0a8bedf6f21"
-  integrity sha1-sdLLI7+wH4Evp7Vb/+iwqL7fbyE=
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-2.1.0.tgz#82b6b218577bb0e67b23abf5c3f7ac73a4b5389f"
+  integrity sha512-ove/QQWSrYOrf9G3xn2MTAjy7PKCtCmm261wpQwecoPAsUtkihkMVczxFqil7VihxgSz4ID9c8bBTsyXR30gQg==
   dependencies:
     github-slugger "^1.1.1"
     mdast-util-to-string "^1.0.2"
@@ -8920,10 +8783,10 @@ merge-stream@^1.0.0:
   dependencies:
     readable-stream "^2.0.1"
 
-merge@^1.1.3:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
-  integrity sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=
+merge@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
+  integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
 
 methods@~1.1.2:
   version "1.1.2"
@@ -8949,24 +8812,24 @@ micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7:
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.0.0, micromatch@^3.1.4:
-  version "3.1.5"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba"
-  integrity sha512-ykttrLPQrz1PUJcXjwsTUjGoPJ64StIGNE2lGVD1c9CuguJ+L7/navsE8IcDNndOoCMvYV0qc/exfVbMHkUhvA==
+micromatch@^3.0.0, micromatch@^3.1.10, micromatch@^3.1.4:
+  version "3.1.10"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
+  integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
   dependencies:
     arr-diff "^4.0.0"
     array-unique "^0.3.2"
-    braces "^2.3.0"
-    define-property "^1.0.0"
-    extend-shallow "^2.0.1"
-    extglob "^2.0.2"
+    braces "^2.3.1"
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
+    extglob "^2.0.4"
     fragment-cache "^0.2.1"
-    kind-of "^6.0.0"
-    nanomatch "^1.2.5"
+    kind-of "^6.0.2"
+    nanomatch "^1.2.9"
     object.pick "^1.3.0"
     regex-not "^1.0.0"
     snapdragon "^0.8.1"
-    to-regex "^3.0.1"
+    to-regex "^3.0.2"
 
 miller-rabin@^4.0.0:
   version "4.0.1"
@@ -8976,49 +8839,32 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@1.40.0:
-  version "1.40.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
-  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
-
-"mime-db@>= 1.30.0 < 2":
-  version "1.32.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414"
-  integrity sha512-+ZWo/xZN40Tt6S+HyakUxnSOgff+JEdaneLWIm0Z6LmpCn5DMcZntLyUY5c/rTDog28LhXLKOUZKoTxTCAdBVw==
+mime-db@1.44.0:
+  version "1.44.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+  integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
 
-mime-db@~1.30.0:
-  version "1.30.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
-  integrity sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=
+"mime-db@>= 1.43.0 < 2":
+  version "1.45.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
+  integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
 
-mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7:
-  version "2.1.17"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
-  integrity sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=
+mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.7:
+  version "2.1.27"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+  integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
   dependencies:
-    mime-db "~1.30.0"
+    mime-db "1.44.0"
 
-mime-types@~2.1.19:
-  version "2.1.24"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
-  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
-  dependencies:
-    mime-db "1.40.0"
-
-mime@1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
-  integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
-
-mime@^1.2.11, mime@^1.3.4, mime@^1.5.0:
+mime@1.6.0, mime@^1.2.11, mime@^1.3.4, mime@^1.5.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
 mimic-fn@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
-  integrity sha1-5md4PZLonb00KBi1IwudYqZyrRg=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
 
 min-document@^2.19.0:
   version "2.19.0"
@@ -9028,14 +8874,14 @@ min-document@^2.19.0:
     dom-walk "^0.1.0"
 
 min-indent@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
-  integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
+  integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
-minimalistic-assert@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
-  integrity sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=
+minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+  integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
 
 minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   version "1.0.1"
@@ -9054,20 +8900,15 @@ minimist@0.0.8:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
 
-minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
   integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
 
-minimist@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
-  integrity sha1-md9lelJXTCHJBXSX33QnkLK0wN4=
-
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-  integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
 mississippi@^2.0.0:
   version "2.0.0"
@@ -9086,20 +8927,27 @@ mississippi@^2.0.0:
     through2 "^2.0.0"
 
 mixin-deep@^1.2.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a"
-  integrity sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
+  integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
   dependencies:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
   dependencies:
     minimist "0.0.8"
 
+mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.0, mkdirp@~0.5.1:
+  version "0.5.5"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+  dependencies:
+    minimist "^1.2.5"
+
 mkdirp@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@@ -9111,9 +8959,9 @@ mkdirp@~0.3.5:
   integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=
 
 mockdate@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-2.0.2.tgz#5ae0c0eaf8fe23e009cd01f9889b42c4f634af12"
-  integrity sha1-WuDA6vj+I+AJzQH5iJtCxPY0rxI=
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-2.0.5.tgz#70c6abf9ed4b2dae65c81dfc170dd1a5cec53620"
+  integrity sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A==
 
 module-deps-sortable@4.0.6:
   version "4.0.6"
@@ -9136,9 +8984,9 @@ module-deps-sortable@4.0.6:
     xtend "^4.0.0"
 
 moment-timezone@^0.5.26:
-  version "0.5.26"
-  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772"
-  integrity sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==
+  version "0.5.31"
+  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05"
+  integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==
   dependencies:
     moment ">= 2.9.0"
 
@@ -9147,15 +8995,20 @@ moment@2.19.3:
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f"
   integrity sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=
 
-moment@2.24.0, moment@2.x.x, "moment@>= 2.9.0":
+moment@2.24.0:
   version "2.24.0"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
   integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
 
-moo@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e"
-  integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==
+moment@2.x.x, "moment@>= 2.9.0":
+  version "2.29.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+  integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
+
+moo@^0.5.0:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
+  integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
 
 move-concurrently@^1.0.1:
   version "1.0.1"
@@ -9179,7 +9032,12 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.0.0, ms@^2.1.1:
+ms@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
+  integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+
+ms@2.1.2, ms@^2.0.0, ms@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -9190,12 +9048,12 @@ multicast-dns-service-types@^1.1.0:
   integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=
 
 multicast-dns@^6.0.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.1.tgz#c5035defa9219d30640558a49298067352098060"
-  integrity sha512-uV3/ckdsffHx9IrGQrx613mturMdMqQ06WTq+C09NsStJ9iNG6RcUWgPKs1Rfjy+idZT6tfQoXEusGNnEZhT3w==
+  version "6.2.3"
+  resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
+  integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==
   dependencies:
-    dns-packet "^1.0.1"
-    thunky "^0.1.0"
+    dns-packet "^1.3.1"
+    thunky "^1.0.2"
 
 mustache@^2.3.2:
   version "2.3.2"
@@ -9216,23 +9074,23 @@ mz@^2.6.0:
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
 
-nan@^2.3.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
-  integrity sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=
+nan@^2.12.1:
+  version "2.14.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
+  integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
 
-nanomatch@^1.2.5:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79"
-  integrity sha512-/5ldsnyurvEw7wNpxLFgjVvBLMta43niEYOy0CJ4ntcYSbx6bugRUTQeFb4BR/WanEL1o3aQgHuVLHQaB6tOqg==
+nanomatch@^1.2.9:
+  version "1.2.13"
+  resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
+  integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
   dependencies:
     arr-diff "^4.0.0"
     array-unique "^0.3.2"
-    define-property "^1.0.0"
-    extend-shallow "^2.0.1"
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
     fragment-cache "^0.2.1"
-    is-odd "^1.0.0"
-    kind-of "^5.0.2"
+    is-windows "^1.0.2"
+    kind-of "^6.0.2"
     object.pick "^1.3.0"
     regex-not "^1.0.0"
     snapdragon "^0.8.1"
@@ -9243,33 +9101,31 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
-ncname@1.0.x:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/ncname/-/ncname-1.0.0.tgz#5b57ad18b1ca092864ef62b0b1ed8194f383b71c"
-  integrity sha1-W1etGLHKCShk72Kwse2BlPODtxw=
-  dependencies:
-    xml-char-classes "^1.0.0"
-
 nearley@^2.7.10:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7"
-  integrity sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==
+  version "2.19.7"
+  resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.7.tgz#eafbe3e2d8ccfe70adaa5c026ab1f9709c116218"
+  integrity sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg==
   dependencies:
     commander "^2.19.0"
-    moo "^0.4.3"
+    moo "^0.5.0"
     railroad-diagrams "^1.0.0"
     randexp "0.4.6"
     semver "^5.4.1"
 
-negotiator@0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
-  integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
+negotiator@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
-neo-async@^2.5.0:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee"
-  integrity sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==
+neo-async@^2.5.0, neo-async@^2.6.0:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+next-tick@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
+  integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
 
 nice-try@^1.0.4:
   version "1.0.5"
@@ -9296,10 +9152,10 @@ node-fetch@^1.0.1:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
-node-forge@0.6.33:
-  version "0.6.33"
-  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc"
-  integrity sha1-RjgRh59XPUUVWtap9D3ClujoXrw=
+node-forge@^0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
+  integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
 
 node-int64@^0.4.0:
   version "0.4.0"
@@ -9307,9 +9163,9 @@ node-int64@^0.4.0:
   integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
 
 "node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
-  integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
+  integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
   dependencies:
     assert "^1.1.1"
     browserify-zlib "^0.2.0"
@@ -9318,10 +9174,10 @@ node-int64@^0.4.0:
     constants-browserify "^1.0.0"
     crypto-browserify "^3.11.0"
     domain-browser "^1.1.1"
-    events "^1.0.0"
+    events "^3.0.0"
     https-browserify "^1.0.0"
     os-browserify "^0.3.0"
-    path-browserify "0.0.0"
+    path-browserify "0.0.1"
     process "^0.11.10"
     punycode "^1.2.4"
     querystring-es3 "^0.2.0"
@@ -9332,13 +9188,13 @@ node-int64@^0.4.0:
     timers-browserify "^2.0.4"
     tty-browserify "0.0.0"
     url "^0.11.0"
-    util "^0.10.3"
-    vm-browserify "0.0.4"
+    util "^0.11.0"
+    vm-browserify "^1.0.1"
 
 node-notifier@^5.0.1, node-notifier@^5.1.2:
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a"
-  integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==
+  version "5.4.3"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
+  integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
   dependencies:
     growly "^1.3.0"
     is-wsl "^1.1.0"
@@ -9346,29 +9202,10 @@ node-notifier@^5.0.1, node-notifier@^5.1.2:
     shellwords "^0.1.1"
     which "^1.3.0"
 
-node-pre-gyp@^0.6.39:
-  version "0.6.39"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
-  integrity sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==
-  dependencies:
-    detect-libc "^1.0.2"
-    hawk "3.1.3"
-    mkdirp "^0.5.1"
-    nopt "^4.0.1"
-    npmlog "^4.0.2"
-    rc "^1.1.7"
-    request "2.81.0"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar "^2.2.1"
-    tar-pack "^3.4.0"
-
-node-releases@^1.1.38:
-  version "1.1.39"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.39.tgz#c1011f30343aff5b633153b10ff691d278d08e8d"
-  integrity sha512-8MRC/ErwNCHOlAFycy9OPca46fQYUjbJRDcZTHVWIGXIjYLM73k70vv3WkYutVnM4cCo4hE0MqBVVZjP6vjISA==
-  dependencies:
-    semver "^6.3.0"
+node-releases@^1.1.61:
+  version "1.1.63"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.63.tgz#db6dbb388544c31e888216304e8fd170efee3ff5"
+  integrity sha512-ukW3iCfQaoxJkSPN+iK7KznTeqDGVJatAEuXsJERYHa9tn/KaT5lBdIyxQjLEVTzSkyjJEuQ17/vaEjrOauDkg==
 
 node-uuid@~1.4.7:
   version "1.4.8"
@@ -9383,21 +9220,13 @@ nomnom@^1.8.1:
     chalk "~0.4.0"
     underscore "~1.6.0"
 
-nopt@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
-  integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
-  dependencies:
-    abbrev "1"
-    osenv "^0.1.4"
-
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
-  integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
+  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
   dependencies:
     hosted-git-info "^2.1.4"
-    is-builtin-module "^1.0.0"
+    resolve "^1.10.0"
     semver "2 || 3 || 4 || 5"
     validate-npm-package-license "^3.0.1"
 
@@ -9408,12 +9237,17 @@ normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
   dependencies:
     remove-trailing-separator "^1.0.1"
 
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
 normalize-range@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
   integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
 
-normalize-url@^1.4.0:
+normalize-url@^1.4.0, normalize-url@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
   integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
@@ -9424,9 +9258,9 @@ normalize-url@^1.4.0:
     sort-keys "^1.0.0"
 
 normalizr@^3.0.2:
-  version "3.2.4"
-  resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.2.4.tgz#16aafc540ca99dc1060ceaa1933556322eac4429"
-  integrity sha1-Fqr8VAypncEGDOqhkzVWMi6sRCk=
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.0.tgz#b8bbc4546ffe43c1c2200503041642915fcd3e1c"
+  integrity sha512-25cd8DiDu+pL46KIaxtVVvvEPjGacJgv0yUg950evr62dQ/ks2JO1kf7+Vi5/rMFjaSTSTls7aCnmRlUSljtiA==
 
 npm-path@^2.0.2:
   version "2.0.4"
@@ -9451,20 +9285,10 @@ npm-which@^3.0.1:
     npm-path "^2.0.2"
     which "^1.2.10"
 
-npmlog@^4.0.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
-  integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
-  dependencies:
-    are-we-there-yet "~1.1.2"
-    console-control-strings "~1.1.0"
-    gauge "~2.7.3"
-    set-blocking "~2.0.0"
-
 nth-check@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4"
-  integrity sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
+  integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
   dependencies:
     boolbase "~1.0.0"
 
@@ -9484,11 +9308,11 @@ number-to-locale-string@^1.0.1:
   integrity sha512-q2BNrXwz250d/FR/wwHIr26oFx5eQiXfKxniS3V31TsSR1rJnYwKPfi7SaHRZYlMIOQTaXkAynjv9Pr/+vfyFQ==
 
 "nwmatcher@>= 1.3.9 < 2.0.0":
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"
-  integrity sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e"
+  integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==
 
-oauth-sign@~0.8.1, oauth-sign@~0.8.2:
+oauth-sign@~0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
   integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
@@ -9513,31 +9337,24 @@ object-copy@^0.1.0:
     kind-of "^3.0.3"
 
 object-hash@^1.1.4:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b"
-  integrity sha512-smRWXzkvxw72VquyZ0wggySl7PFUtoDhvhpdwgESXxUrH7vVhhp9asfup1+rVLrhsl7L45Ee1Q/l5R2Ul4MwUg==
-
-object-inspect@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
-  integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
-
-object-inspect@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
-  integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
+  integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
 
-object-is@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
-  integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=
+object-inspect@^1.7.0, object-inspect@^1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
+  integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
 
-object-keys@^1.0.11, object-keys@^1.0.12:
-  version "1.0.12"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
-  integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==
+object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81"
+  integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
 
-object-keys@^1.1.1:
+object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -9549,43 +9366,42 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
-object.assign@^4.0.4, object.assign@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
-  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
+object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
+  integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
   dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.1"
-    has-symbols "^1.0.0"
-    object-keys "^1.0.11"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.0"
+    has-symbols "^1.0.1"
+    object-keys "^1.1.1"
 
-object.entries@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f"
-  integrity sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=
+object.entries@^1.0.4, object.entries@^1.1.1, object.entries@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add"
+  integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==
   dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.6.1"
-    function-bind "^1.1.0"
-    has "^1.0.1"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.5"
+    has "^1.0.3"
 
-object.fromentries@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
-  integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==
+object.fromentries@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9"
+  integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==
   dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.11.0"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
     function-bind "^1.1.1"
-    has "^1.0.1"
+    has "^1.0.3"
 
-object.getownpropertydescriptors@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
-  integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=
+object.getownpropertydescriptors@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
+  integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
   dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.5.1"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
 
 object.omit@^2.0.0:
   version "2.0.1"
@@ -9602,17 +9418,7 @@ object.pick@^1.3.0:
   dependencies:
     isobject "^3.0.1"
 
-object.values@^1.0.4, object.values@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9"
-  integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.12.0"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-
-object.values@^1.1.1:
+object.values@^1.0.4, object.values@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
   integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
@@ -9622,10 +9428,10 @@ object.values@^1.1.1:
     function-bind "^1.1.1"
     has "^1.0.3"
 
-obuf@^1.0.0, obuf@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
-  integrity sha1-EEEktsYCxnlogaBCVB0220OlJk4=
+obuf@^1.0.0, obuf@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
+  integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
 on-finished@~2.3.0:
   version "2.3.0"
@@ -9634,12 +9440,12 @@ on-finished@~2.3.0:
   dependencies:
     ee-first "1.1.1"
 
-on-headers@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
-  integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=
+on-headers@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+  integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
 
-once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@@ -9662,31 +9468,23 @@ onetime@^1.0.0:
   integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=
 
 opn@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519"
-  integrity sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
+  integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
   dependencies:
     is-wsl "^1.1.0"
 
-optimist@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
-  integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
-  dependencies:
-    minimist "~0.0.1"
-    wordwrap "~0.0.2"
-
 optionator@^0.8.1, optionator@^0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
-  integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
+  integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
   dependencies:
     deep-is "~0.1.3"
-    fast-levenshtein "~2.0.4"
+    fast-levenshtein "~2.0.6"
     levn "~0.3.0"
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
-    wordwrap "~1.0.0"
+    word-wrap "~1.2.3"
 
 options@>=0.0.5:
   version "0.0.6"
@@ -9712,11 +9510,11 @@ ordered-read-streams@^0.3.0:
     readable-stream "^2.0.1"
 
 original@>=0.0.5:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
-  integrity sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
+  integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
   dependencies:
-    url-parse "1.0.x"
+    url-parse "^1.4.3"
 
 os-browserify@^0.3.0:
   version "0.3.0"
@@ -9744,19 +9542,11 @@ os-locale@^2.0.0:
     lcid "^1.0.0"
     mem "^1.1.0"
 
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1:
+os-tmpdir@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
-osenv@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
-  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.0"
-
 output-file-sync@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
@@ -9772,16 +9562,16 @@ p-finally@^1.0.0:
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
 p-limit@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
-  integrity sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
   dependencies:
     p-try "^1.0.0"
 
-p-limit@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
-  integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
+p-limit@^2.0.0, p-limit@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+  integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
   dependencies:
     p-try "^2.0.0"
 
@@ -9799,6 +9589,13 @@ p-locate@^3.0.0:
   dependencies:
     p-limit "^2.0.0"
 
+p-locate@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+  dependencies:
+    p-limit "^2.2.0"
+
 p-map@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
@@ -9815,16 +9612,16 @@ p-try@^2.0.0:
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
 pako@~1.0.5:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
-  integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+  integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
 
 parallel-transform@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
-  integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
+  integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==
   dependencies:
-    cyclist "~0.2.2"
+    cyclist "^1.0.1"
     inherits "^2.0.3"
     readable-stream "^2.1.5"
 
@@ -9849,16 +9646,16 @@ parents@^1.0.0:
   dependencies:
     path-platform "~0.11.15"
 
-parse-asn1@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
-  integrity sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=
+parse-asn1@^5.0.0, parse-asn1@^5.1.5:
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
+  integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
   dependencies:
-    asn1.js "^4.0.0"
+    asn1.js "^5.2.0"
     browserify-aes "^1.0.0"
-    create-hash "^1.1.0"
     evp_bytestokey "^1.0.0"
     pbkdf2 "^3.0.3"
+    safe-buffer "^5.1.1"
 
 parse-entities@^1.0.2, parse-entities@^1.1.0:
   version "1.2.2"
@@ -9905,22 +9702,40 @@ parse-json@^2.2.0:
   dependencies:
     error-ex "^1.2.0"
 
+parse-json@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
+  integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
+  dependencies:
+    error-ex "^1.3.1"
+    json-parse-better-errors "^1.0.1"
+
 parse-json@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f"
-  integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646"
+  integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     error-ex "^1.3.1"
-    json-parse-better-errors "^1.0.1"
+    json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parse-url@^1.3.0:
-  version "1.3.11"
-  resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-1.3.11.tgz#57c15428ab8a892b1f43869645c711d0e144b554"
-  integrity sha1-V8FUKKuKiSsfQ4aWRccR0OFEtVQ=
+parse-path@^3.0.1:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-3.0.4.tgz#a48b7b529da41f34d9d1428602a39b29fc7180e4"
+  integrity sha512-wP70vtwv2DyrM2YoA7ZHVv4zIXa4P7dGgHlj+VwyXNDduLLVJ7NMY1zsFxjUUJ3DAwJLupGb1H5gMDDiNlJaxw==
+  dependencies:
+    is-ssh "^1.3.0"
+    protocols "^1.4.0"
+
+parse-url@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-3.0.2.tgz#602787a7063a795d72b8673197505e72f60610be"
+  integrity sha1-YCeHpwY6eV1yuGcxl1BecvYGEL4=
   dependencies:
     is-ssh "^1.3.0"
+    normalize-url "^1.9.1"
+    parse-path "^3.0.1"
     protocols "^1.4.0"
 
 parse5@^1.5.1:
@@ -9935,10 +9750,10 @@ parse5@^3.0.1:
   dependencies:
     "@types/node" "*"
 
-parseurl@~1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
-  integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=
+parseurl@~1.3.2, parseurl@~1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
 pascalcase@^0.1.1:
   version "0.1.1"
@@ -9946,17 +9761,20 @@ pascalcase@^0.1.1:
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
 password-generator@^2.0.1:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/password-generator/-/password-generator-2.2.0.tgz#fc75cff795110923e054a5a71623433240bf5e49"
-  integrity sha1-/HXP95URCSPgVKWnFiNDMkC/Xkk=
-  dependencies:
-    yargs-parser "^8.0.0"
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/password-generator/-/password-generator-2.3.2.tgz#9626f778d64d26f7c2f73b64389407e28f62eecd"
+  integrity sha512-kJWUrdveSAqHCeWJWnv5vNc89hFHM5au+pvKja5+xCTxlRF3zQaecJlR6hSoOotAJtQ3otQq4/Q4iWc/TxsXhA==
 
 path-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
   integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=
 
+path-browserify@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
+  integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
+
 path-dirname@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
@@ -9974,6 +9792,11 @@ path-exists@^3.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
+path-exists@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
 path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -10032,15 +9855,22 @@ path-type@^2.0.0:
   dependencies:
     pify "^2.0.0"
 
+path-type@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+  integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
+  dependencies:
+    pify "^3.0.0"
+
 path-type@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
 pbkdf2@^3.0.3:
-  version "3.0.14"
-  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
-  integrity sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
+  integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==
   dependencies:
     create-hash "^1.1.2"
     create-hmac "^1.1.4"
@@ -10063,6 +9893,11 @@ performance-now@^2.1.0:
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
+picomatch@^2.0.4, picomatch@^2.2.1:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+  integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
 pify@^2.0.0, pify@^2.2.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -10144,13 +9979,13 @@ pluralize@^1.2.1:
   integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=
 
 portfinder@^1.0.9:
-  version "1.0.13"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
-  integrity sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=
+  version "1.0.28"
+  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
+  integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
   dependencies:
-    async "^1.5.2"
-    debug "^2.2.0"
-    mkdirp "0.5.x"
+    async "^2.6.2"
+    debug "^3.1.1"
+    mkdirp "^0.5.5"
 
 posix-character-classes@^0.1.0:
   version "0.1.1"
@@ -10370,12 +10205,11 @@ postcss-discard-unused@^2.2.1:
     uniqs "^2.0.0"
 
 postcss-filter-plugins@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
-  integrity sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec"
+  integrity sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==
   dependencies:
     postcss "^5.0.4"
-    uniqid "^4.0.0"
 
 postcss-font-family-system-ui@^1.0.1:
   version "1.0.2"
@@ -10421,41 +10255,23 @@ postcss-initial@^1.3.1:
     lodash.template "^4.2.4"
     postcss "^5.0.19"
 
-postcss-load-config@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
-  integrity sha1-U56a/J3chiASHr+djDZz4M5Q0oo=
-  dependencies:
-    cosmiconfig "^2.1.0"
-    object-assign "^4.1.0"
-    postcss-load-options "^1.2.0"
-    postcss-load-plugins "^2.3.0"
-
-postcss-load-options@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c"
-  integrity sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=
-  dependencies:
-    cosmiconfig "^2.1.0"
-    object-assign "^4.1.0"
-
-postcss-load-plugins@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92"
-  integrity sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=
+postcss-load-config@^2.0.0:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
+  integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==
   dependencies:
-    cosmiconfig "^2.1.1"
-    object-assign "^4.1.0"
+    cosmiconfig "^5.0.0"
+    import-cwd "^2.0.0"
 
 postcss-loader@^2.0.8:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.10.tgz#090db0540140bd56a7a7f717c41bc29aeef4c674"
-  integrity sha512-xQaDcEgJ/2JqFY18zpFkik8vyYs7oS5ZRbrjvDqkP97k2wYWfPT4+qA0m4o3pTSCsz0u26PNqs8ZO9FRUWAqrA==
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.6.tgz#1d7dd7b17c6ba234b9bed5af13e0bea40a42d740"
+  integrity sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==
   dependencies:
     loader-utils "^1.1.0"
     postcss "^6.0.0"
-    postcss-load-config "^1.2.0"
-    schema-utils "^0.3.0"
+    postcss-load-config "^2.0.0"
+    schema-utils "^0.4.0"
 
 postcss-media-minmax@^2.1.0:
   version "2.1.2"
@@ -10538,10 +10354,10 @@ postcss-minify-selectors@^2.0.4:
     postcss "^5.0.14"
     postcss-selector-parser "^2.0.0"
 
-postcss-modules-extract-imports@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb"
-  integrity sha1-thTJcgvmgW6u41+zpfqh26agXds=
+postcss-modules-extract-imports@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a"
+  integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==
   dependencies:
     postcss "^6.0.1"
 
@@ -10712,9 +10528,9 @@ postcss-url@^6.0.4:
     xxhashjs "^0.2.1"
 
 postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
-  integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
+  integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
 postcss-zindex@^2.0.1:
   version "2.2.0"
@@ -10745,13 +10561,13 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.
     supports-color "^3.2.3"
 
 postcss@^6.0.0, postcss@^6.0.1:
-  version "6.0.16"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.16.tgz#112e2fe2a6d2109be0957687243170ea5589e146"
-  integrity sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==
+  version "6.0.23"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
+  integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
   dependencies:
-    chalk "^2.3.0"
+    chalk "^2.4.1"
     source-map "^0.6.1"
-    supports-color "^5.1.0"
+    supports-color "^5.4.0"
 
 prelude-ls@~1.1.2:
   version "1.1.2"
@@ -10788,17 +10604,7 @@ pretty-format@^19.0.0:
   dependencies:
     ansi-styles "^3.0.0"
 
-pretty-format@^24.0.0, pretty-format@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
-  integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
-  dependencies:
-    "@jest/types" "^24.8.0"
-    ansi-regex "^4.0.0"
-    ansi-styles "^3.2.0"
-    react-is "^16.8.4"
-
-pretty-format@^24.3.0, pretty-format@^24.9.0:
+pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
   integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
@@ -10808,41 +10614,46 @@ pretty-format@^24.3.0, pretty-format@^24.9.0:
     ansi-styles "^3.2.0"
     react-is "^16.8.4"
 
+pretty-format@^25.1.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+  integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+  dependencies:
+    "@jest/types" "^25.5.0"
+    ansi-regex "^5.0.0"
+    ansi-styles "^4.0.0"
+    react-is "^16.12.0"
+
 pretty-format@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237"
-  integrity sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.5.2.tgz#5d896acfdaa09210683d34b6dc0e6e21423cd3e1"
+  integrity sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     ansi-regex "^5.0.0"
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
 
-private@^0.1.6, private@^0.1.7, private@^0.1.8, private@~0.1.5:
+private@^0.1.6, private@^0.1.8, private@~0.1.5:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
   integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
 
-process-nextick-args@^1.0.6, process-nextick-args@~1.0.6:
+process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+process-nextick-args@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
   integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=
 
-process-nextick-args@~2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
-  integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
-
 process@^0.11.10:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-process@~0.5.1:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
-  integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
-
 progress@^1.1.8:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
@@ -10872,6 +10683,15 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
+prop-types-exact@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869"
+  integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==
+  dependencies:
+    has "^1.0.3"
+    object.assign "^4.1.0"
+    reflect.ownkeys "^0.2.0"
+
 prop-types@15.5.8:
   version "15.5.8"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
@@ -10879,7 +10699,7 @@ prop-types@15.5.8:
   dependencies:
     fbjs "^0.8.9"
 
-prop-types@15.x, prop-types@^15.6.0, prop-types@^15.6.1:
+prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -10888,31 +10708,23 @@ prop-types@15.x, prop-types@^15.6.0, prop-types@^15.6.1:
     object-assign "^4.1.1"
     react-is "^16.8.1"
 
-prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
-  integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
-  dependencies:
-    loose-envify "^1.3.1"
-    object-assign "^4.1.1"
-
 property-information@^3.1.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/property-information/-/property-information-3.2.0.tgz#fd1483c8fbac61808f5fe359e7693a1f48a58331"
   integrity sha1-/RSDyPusYYCPX+NZ52k6H0ilgzE=
 
 protocols@^1.1.0, protocols@^1.4.0:
-  version "1.4.6"
-  resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.6.tgz#f8bb263ea1b5fd7a7604d26b8be39bd77678bf8a"
-  integrity sha1-+LsmPqG1/Xp2BNJri+Ob13Z4v4o=
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"
+  integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==
 
-proxy-addr@~2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
-  integrity sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=
+proxy-addr@~2.0.5:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
+  integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
   dependencies:
     forwarded "~0.1.2"
-    ipaddr.js "1.5.2"
+    ipaddr.js "1.9.1"
 
 proxymise@^1.0.2:
   version "1.0.2"
@@ -10929,31 +10741,24 @@ pseudomap@^1.0.2:
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
   integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
 
-psl@^1.1.24:
+psl@^1.1.24, psl@^1.1.28:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
   integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
 
 public-encrypt@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
-  integrity sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
+  integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
   dependencies:
     bn.js "^4.1.0"
     browserify-rsa "^4.0.0"
     create-hash "^1.1.0"
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
+    safe-buffer "^5.1.2"
 
-pump@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
-  integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
-pump@^2.0.1:
+pump@^2.0.0, pump@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
   integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
@@ -10962,13 +10767,13 @@ pump@^2.0.1:
     once "^1.3.1"
 
 pumpify@^1.3.3:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b"
-  integrity sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+  integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
   dependencies:
-    duplexify "^3.1.2"
-    inherits "^2.0.1"
-    pump "^1.0.0"
+    duplexify "^3.6.0"
+    inherits "^2.0.3"
+    pump "^2.0.0"
 
 punycode@1.3.2:
   version "1.3.2"
@@ -10980,7 +10785,7 @@ punycode@^1.2.4, punycode@^1.4.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -10990,21 +10795,21 @@ q@^1.0.1, q@^1.1.2:
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
   integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
 
-qs@6.5.1, qs@^6.4.0, qs@~6.5.1:
-  version "6.5.1"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
-  integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==
+qs@6.7.0:
+  version "6.7.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
+  integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+
+qs@^6.4.0:
+  version "6.9.4"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
+  integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
 
 qs@~6.3.0:
   version "6.3.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
   integrity sha1-51vV9uJoEioqDgvaYwslUMFmUCw=
 
-qs@~6.4.0:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
-  integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=
-
 qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -11028,20 +10833,15 @@ querystring@0.2.0:
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
   integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
 
-querystringify@0.0.x:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
-  integrity sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=
-
-querystringify@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
-  integrity sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=
+querystringify@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+  integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
-raf@^3.1.0, raf@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
-  integrity sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==
+raf@^3.1.0, raf@^3.4.0, raf@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
   dependencies:
     performance-now "^2.1.0"
 
@@ -11063,42 +10863,43 @@ randexp@0.4.6:
     discontinuous-range "1.0.0"
     ret "~0.1.10"
 
-randomatic@^1.1.3:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
-  integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==
+randomatic@^3.0.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
+  integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
   dependencies:
-    is-number "^3.0.0"
-    kind-of "^4.0.0"
+    is-number "^4.0.0"
+    kind-of "^6.0.0"
+    math-random "^1.0.1"
 
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79"
-  integrity sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
   dependencies:
     safe-buffer "^5.1.0"
 
 randomfill@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.3.tgz#b96b7df587f01dd91726c418f30553b1418e3d62"
-  integrity sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
+  integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==
   dependencies:
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
-range-parser@^1.0.3, range-parser@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
-  integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
+range-parser@^1.0.3, range-parser@~1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
 
-raw-body@2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
-  integrity sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=
+raw-body@2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
+  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
   dependencies:
-    bytes "3.0.0"
-    http-errors "1.6.2"
-    iconv-lite "0.4.19"
+    bytes "3.1.0"
+    http-errors "1.7.2"
+    iconv-lite "0.4.24"
     unpipe "1.0.0"
 
 raw-body@~1.1.0:
@@ -11109,16 +10910,6 @@ raw-body@~1.1.0:
     bytes "1"
     string_decoder "0.10"
 
-rc@^1.1.7:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092"
-  integrity sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=
-  dependencies:
-    deep-extend "~0.4.0"
-    ini "~1.3.0"
-    minimist "^1.2.0"
-    strip-json-comments "~2.0.1"
-
 re-reselect@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd"
@@ -11148,9 +10939,9 @@ react-collapse@^4.0.3:
     prop-types "^15.5.8"
 
 react-color@^2.14.1:
-  version "2.18.0"
-  resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.18.0.tgz#34956f0bac394f6c3bc01692fd695644cc775ffd"
-  integrity sha512-FyVeU1kQiSokWc8NPz22azl1ezLpJdUyTbWL0LPUpcuuYDrZ/Y1veOk9rRK5B3pMlyDGvTk4f4KJhlkIQNRjEA==
+  version "2.18.1"
+  resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.18.1.tgz#2cda8cc8e06a9e2c52ad391a30ddad31972472f4"
+  integrity sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==
   dependencies:
     "@icons/material" "^0.2.4"
     lodash "^4.17.11"
@@ -11160,9 +10951,9 @@ react-color@^2.14.1:
     tinycolor2 "^1.4.1"
 
 react-copy-to-clipboard@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz#8eae107bb400be73132ed3b6a7b4fb156090208e"
-  integrity sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz#d82a437e081e68dfca3761fbd57dbf2abdda1316"
+  integrity sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg==
   dependencies:
     copy-to-clipboard "^3"
     prop-types "^15.5.8"
@@ -11191,9 +10982,9 @@ react-dnd@3:
     shallowequal "^1.0.2"
 
 react-dom@15:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730"
-  integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=
+  version "15.7.0"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.7.0.tgz#39106dee996d0742fb0f43d567ef8b8153483ab2"
+  integrity sha512-mpjXqC2t1FuYsILOLCj0kg6pbg460byZkVA/80VtDmKU/pYmoTdHOtaMcTRIDiyXLz4sIur0cQ04nOC6iGndJg==
   dependencies:
     fbjs "^0.8.9"
     loose-envify "^1.1.0"
@@ -11209,20 +11000,20 @@ react-draggable@^3.3.2:
     prop-types "^15.6.0"
 
 react-draggable@^4.0.3:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.1.0.tgz#e1c5b774001e32f0bff397254e1e9d5448ac92a4"
-  integrity sha512-Or/qe70cfymshqoC8Lsp0ukTzijJObehb7Vfl7tb5JRxoV+b6PDkOGoqYaWBzZ59k9dH/bwraLGsnlW78/3vrA==
+  version "4.4.3"
+  resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3"
+  integrity sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==
   dependencies:
     classnames "^2.2.5"
     prop-types "^15.6.0"
 
 react-element-to-jsx-string@^13.1.0:
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-13.1.0.tgz#a8dbd1a2aeec7ab58f380644f6b5e7e8c7c79c8f"
-  integrity sha512-IlWivaMn9zkBa5+TZGW4+5+qeQePveQZBFF5UQQdEGm6Ca+c0uwuZgIcFqP27hYekI4kuQiwG/Hav8iGJKmzsg==
+  version "13.2.0"
+  resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-13.2.0.tgz#550bb9670e4d8c82977934c14db14469d156a70d"
+  integrity sha512-u994HvBg4YFlH48IfXts0gwA3ZnfAY4SAWISLyizdPL84xW5CIfvYfOq0bQdg3LCeLZNC+zkniAbqrr3mDXDVw==
   dependencies:
     is-plain-object "2.0.4"
-    stringify-object "3.2.1"
+    stringify-object "3.2.2"
 
 react-hot-api@^0.4.5:
   version "0.4.7"
@@ -11237,21 +11028,11 @@ react-hot-loader@^1.3.0:
     react-hot-api "^0.4.5"
     source-map "^0.4.4"
 
-react-is@^16.12.0:
+react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.3.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-is@^16.3.1, react-is@^16.7.0, react-is@^16.8.4:
-  version "16.8.6"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
-  integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
-
-react-is@^16.8.1:
-  version "16.12.0"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
-  integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
-
 react-lazy-cache@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/react-lazy-cache/-/react-lazy-cache-3.0.1.tgz#0dc64d38df1767ef77678c5c94190064cb11b0cd"
@@ -11259,6 +11040,11 @@ react-lazy-cache@^3.0.1:
   dependencies:
     deep-equal "^1.0.1"
 
+react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+  integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
 react-markdown@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-3.6.0.tgz#29f6aaab5270c8ef0a5e234093a873ec3e01722b"
@@ -11282,21 +11068,22 @@ react-motion@^0.4.5:
     raf "^3.1.0"
 
 react-redux@^5.0.4:
-  version "5.0.6"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
-  integrity sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"
+  integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==
   dependencies:
-    hoist-non-react-statics "^2.2.1"
-    invariant "^2.0.0"
-    lodash "^4.2.0"
-    lodash-es "^4.2.0"
+    "@babel/runtime" "^7.1.2"
+    hoist-non-react-statics "^3.3.0"
+    invariant "^2.2.4"
     loose-envify "^1.1.0"
-    prop-types "^15.5.10"
+    prop-types "^15.6.1"
+    react-is "^16.6.0"
+    react-lifecycles-compat "^3.0.0"
 
 react-resizable@^1.9.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.9.0.tgz#7426f06852180e6844bbbc32ead67760d2904a65"
-  integrity sha512-YAls7H4+34MMDg2cPnhVfLtes//XWvrLNuxbVwawwJFIGlPvhGi8BLCcIsDdLJ2CCCxtymVotk2Hq4RtPJ7t5g==
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.11.0.tgz#0b237c4aff16937b7663de1045861749683227ad"
+  integrity sha512-VoGz2ddxUFvildS8r8/29UZJeyiM3QJnlmRZSuXm+FpTqq/eIrMPc796Y9XQLg291n2hFZJtIoP1xC3hSTw/jg==
   dependencies:
     prop-types "15.x"
     react-draggable "^4.0.3"
@@ -11318,16 +11105,17 @@ react-router-redux@^4.0.8:
   integrity sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=
 
 react-router@3:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.0.tgz#62b6279d589b70b34e265113e4c0a9261a02ed36"
-  integrity sha512-sXlLOg0TRCqnjCVskqBHGjzNjcJKUqXEKnDSuxMYJSPJNq9hROE9VsiIW2kfIq7Ev+20Iz0nxayekXyv0XNmsg==
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.6.tgz#cad202796a7bba3efc2100da453b3379c9d4aeb4"
+  integrity sha512-nlxtQE8B22hb/JxdaslI1tfZacxFU8x8BJryXOnR2RxB4vc01zuHYAHAIgmBkdk1kzXaA25hZxK6KAH/+CXArw==
   dependencies:
     create-react-class "^15.5.1"
     history "^3.0.0"
-    hoist-non-react-statics "^1.2.0"
+    hoist-non-react-statics "^3.3.2"
     invariant "^2.2.1"
     loose-envify "^1.2.0"
-    prop-types "^15.5.6"
+    prop-types "^15.7.2"
+    react-is "^16.13.0"
     warning "^3.0.0"
 
 react-sortable-hoc@^0.6.8:
@@ -11341,9 +11129,9 @@ react-sortable-hoc@^0.6.8:
     prop-types "^15.5.7"
 
 react-test-renderer@15:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.2.tgz#d0333434fc2c438092696ca770da5ed48037efa8"
-  integrity sha1-0DM0NPwsQ4CSaWyncNpe1IA376g=
+  version "15.7.0"
+  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.7.0.tgz#d52a29b175706ff7ea49c98058ebaafa39321812"
+  integrity sha512-sRkDsQA9AhmRZFmpnhYnm7C9h3f7m/KhVhAGQWQfER8AF6ANNsmGINf+7AXBNbDd0DKxSit6VFTO/gsjRwT5lg==
   dependencies:
     fbjs "^0.8.9"
     object-assign "^4.1.0"
@@ -11367,20 +11155,21 @@ react-transition-group@1:
     warning "^3.0.0"
 
 react-virtualized@^9.7.2:
-  version "9.16.1"
-  resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.16.1.tgz#2a9c8262094bc5cca309a3ee29f2b4b572ae95a1"
-  integrity sha512-79dhzAW6ftlcrHQNfCspbuOFTbmyBzFLxvJ+oxADP0ew+m1W8YdsyZwRLzp9cZWjH0lvXJaxtX56x1mPmxgBFw==
+  version "9.22.2"
+  resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.2.tgz#217a870bad91e5438f46f01a009e1d8ce1060a5a"
+  integrity sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw==
   dependencies:
-    babel-runtime "^6.26.0"
-    classnames "^2.2.3"
-    dom-helpers "^2.4.0 || ^3.0.0"
-    loose-envify "^1.3.0"
-    prop-types "^15.5.4"
+    "@babel/runtime" "^7.7.2"
+    clsx "^1.0.4"
+    dom-helpers "^5.1.3"
+    loose-envify "^1.4.0"
+    prop-types "^15.7.2"
+    react-lifecycles-compat "^3.0.4"
 
 react@15:
-  version "15.6.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
-  integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=
+  version "15.7.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-15.7.0.tgz#10308fd42ac6912a250bf00380751abc41ac7106"
+  integrity sha512-5/MMRYmpmM0sMTHGLossnJCrmXQIiJilD6y3YN3TzAwGFj6zdnMtFv6xmi65PHKRV+pehIHpT7oy67Sr6s9AHA==
   dependencies:
     create-react-class "^15.6.0"
     fbjs "^0.8.9"
@@ -11389,14 +11178,13 @@ react@15:
     prop-types "^15.5.10"
 
 react@>=16.0.0:
-  version "16.3.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
-  integrity sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==
+  version "16.14.0"
+  resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
+  integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
   dependencies:
-    fbjs "^0.8.16"
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    prop-types "^15.6.0"
+    prop-types "^15.6.2"
 
 reactcss@^1.2.0:
   version "1.2.3"
@@ -11446,10 +11234,19 @@ read-pkg@^2.0.0:
     normalize-package-data "^2.3.2"
     path-type "^2.0.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9, readable-stream@^2.3.3:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
-  integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
+read-pkg@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
+  integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
+  dependencies:
+    load-json-file "^4.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^3.0.0"
+
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+  version "2.3.7"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
   dependencies:
     core-util-is "~1.0.0"
     inherits "~2.0.3"
@@ -11459,7 +11256,7 @@ read-pkg@^2.0.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0":
+"readable-stream@>=1.0.33-1 <1.1.0-0":
   version "1.0.34"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
   integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
@@ -11469,10 +11266,10 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0":
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^3.2.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9"
-  integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==
+readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.2.0, readable-stream@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+  integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
   dependencies:
     inherits "^2.0.3"
     string_decoder "^1.1.1"
@@ -11503,15 +11300,21 @@ readable-stream@~2.1.0:
     string_decoder "~0.10.x"
     util-deprecate "~1.0.1"
 
-readdirp@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
-  integrity sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=
+readdirp@^2.0.0, readdirp@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
+  integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==
   dependencies:
-    graceful-fs "^4.1.2"
-    minimatch "^3.0.2"
+    graceful-fs "^4.1.11"
+    micromatch "^3.1.10"
     readable-stream "^2.0.2"
-    set-immediate-shim "^1.0.1"
+
+readdirp@~3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
+  integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
+  dependencies:
+    picomatch "^2.2.1"
 
 readline2@^1.0.1:
   version "1.0.1"
@@ -11542,12 +11345,12 @@ recast@^0.14.1:
     private "~0.1.5"
     source-map "~0.6.1"
 
-recast@^0.17.6:
-  version "0.17.6"
-  resolved "https://registry.yarnpkg.com/recast/-/recast-0.17.6.tgz#64ae98d0d2dfb10ff92ff5fb9ffb7371823b69fa"
-  integrity sha512-yoQRMRrK1lszNtbkGyM4kN45AwylV5hMiuEveUBlxytUViWevjvX6w+tzJt1LH4cfUhWt4NZvy3ThIhu6+m5wQ==
+recast@^0.18.7:
+  version "0.18.10"
+  resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.10.tgz#605ebbe621511eb89b6356a7e224bff66ed91478"
+  integrity sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==
   dependencies:
-    ast-types "0.12.4"
+    ast-types "0.13.3"
     esprima "~4.0.0"
     private "^0.1.8"
     source-map "~0.6.1"
@@ -11595,26 +11398,27 @@ reduce-css-calc@^1.2.6, reduce-css-calc@^1.2.7:
     reduce-function-call "^1.0.1"
 
 reduce-function-call@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
-  integrity sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f"
+  integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==
   dependencies:
-    balanced-match "^0.4.2"
+    balanced-match "^1.0.0"
 
-reduce-reducers@^0.1.0:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b"
-  integrity sha1-+htHGLxSkqcd3R5dg5yb6pdw8Us=
+reduce-reducers@^0.4.3:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c"
+  integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==
 
 redux-actions@^2.0.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.2.1.tgz#d64186b25649a13c05478547d7cd7537b892410d"
-  integrity sha1-1kGGslZJoTwFR4VH1811N7iSQQ0=
+  version "2.6.5"
+  resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e"
+  integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw==
   dependencies:
-    invariant "^2.2.1"
-    lodash "^4.13.1"
-    lodash-es "^4.17.4"
-    reduce-reducers "^0.1.0"
+    invariant "^2.2.4"
+    just-curry-it "^3.1.0"
+    loose-envify "^1.4.0"
+    reduce-reducers "^0.4.3"
+    to-camel-case "^1.0.0"
 
 redux-auth-wrapper@^1.0.0:
   version "1.1.0"
@@ -11626,12 +11430,12 @@ redux-auth-wrapper@^1.0.0:
     prop-types "15.5.8"
 
 redux-form@5:
-  version "5.3.6"
-  resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-5.3.6.tgz#f77a81dbf38d44d26ea411100a23f19e29cd1946"
-  integrity sha1-93qB2/ONRNJupBEQCiPxninNGUY=
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-5.4.0.tgz#daac6b47ab20cbfb5ffdcee79f1115623a6c86a7"
+  integrity sha512-Ssftdf3Or6nwEBl1SqgPbZdxhmZC8AejHTTLnt1R6SfCqyoHgPnPyxu8NuewS2KvDN925nwRT9dGxeIYDgtAWQ==
   dependencies:
     deep-equal "^1.0.1"
-    hoist-non-react-statics "^1.0.5"
+    hoist-non-react-statics "^2.3.1"
     invariant "^2.0.0"
     is-promise "^2.1.0"
     prop-types "^15.5.8"
@@ -11659,16 +11463,16 @@ redux-router@^2.1.2:
     deep-equal "^1.0.1"
 
 redux-thunk@^2.0.1:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
-  integrity sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
+  integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
 
 redux@*, redux@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03"
-  integrity sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
+  integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
   dependencies:
-    loose-envify "^1.1.0"
+    loose-envify "^1.4.0"
     symbol-observable "^1.2.0"
 
 redux@^3.5.2:
@@ -11681,35 +11485,35 @@ redux@^3.5.2:
     loose-envify "^1.1.0"
     symbol-observable "^1.0.3"
 
-regenerate-unicode-properties@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
-  integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
+reflect.ownkeys@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
+  integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
+
+regenerate-unicode-properties@^8.2.0:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
+  integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
   dependencies:
     regenerate "^1.4.0"
 
-regenerate@^1.2.1:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
-  integrity sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==
+regenerate@^1.2.1, regenerate@^1.4.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f"
+  integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==
 
-regenerate@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
-  integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-
-regenerator-preset@^0.13.0:
-  version "0.13.0"
-  resolved "https://registry.yarnpkg.com/regenerator-preset/-/regenerator-preset-0.13.0.tgz#9ffc2636dc3cb7e905c40dc530c402a5c9ffa44d"
-  integrity sha512-uqMjGykE4ra4rhmglGTfgKVL4HCrXW/pW6/DKR+4xwH3bkoGTySDXP6eNB/GiwSo49WV7T1pGt3Ca6Z/RBme0w==
-  dependencies:
-    "@babel/plugin-proposal-function-sent" "^7.2.0"
-    "@babel/plugin-syntax-async-generators" "^7.2.0"
-    "@babel/plugin-transform-arrow-functions" "^7.2.0"
-    "@babel/plugin-transform-block-scoping" "^7.4.4"
-    "@babel/plugin-transform-classes" "^7.4.4"
-    "@babel/plugin-transform-for-of" "^7.4.4"
-    regenerator-transform "^0.14.0"
+regenerator-preset@^0.13.2:
+  version "0.13.2"
+  resolved "https://registry.yarnpkg.com/regenerator-preset/-/regenerator-preset-0.13.2.tgz#98006b7de35860850270707c31f20a63ed5a7535"
+  integrity sha512-oPYBaPZx5VbkK1kxtImXNzfBwOQG25CIFm+vCKCSspAc8wSpts8aXWRKQTtcJtZ06ZvCYeMB9VFiOfpxLzSjcw==
+  dependencies:
+    "@babel/plugin-proposal-function-sent" "^7.8.3"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
+    "@babel/plugin-transform-arrow-functions" "^7.8.3"
+    "@babel/plugin-transform-block-scoping" "^7.8.3"
+    "@babel/plugin-transform-classes" "^7.8.3"
+    "@babel/plugin-transform-for-of" "^7.8.4"
+    regenerator-transform "^0.14.2"
 
 regenerator-runtime@^0.10.5:
   version "0.10.5"
@@ -11721,15 +11525,10 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-runtime@^0.13.2:
-  version "0.13.2"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
-  integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
-
-regenerator-runtime@^0.13.4:
-  version "0.13.5"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
-  integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
+  version "0.13.7"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+  integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
 
 regenerator-transform@^0.10.0:
   version "0.10.1"
@@ -11740,27 +11539,27 @@ regenerator-transform@^0.10.0:
     babel-types "^6.19.0"
     private "^0.1.6"
 
-regenerator-transform@^0.14.0:
-  version "0.14.0"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.0.tgz#2ca9aaf7a2c239dd32e4761218425b8c7a86ecaf"
-  integrity sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==
+regenerator-transform@^0.14.2, regenerator-transform@^0.14.5:
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+  integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
   dependencies:
-    private "^0.1.6"
+    "@babel/runtime" "^7.8.4"
 
 regenerator@^0.14.1:
-  version "0.14.1"
-  resolved "https://registry.yarnpkg.com/regenerator/-/regenerator-0.14.1.tgz#58b8f72337622ade79c79c6b55a82f47e2f96fd5"
-  integrity sha512-gbvT/2NduvWa7hw+v5R0tETaFRvbvPAiTLoqMPjcaP5bvBG55hQHDxKwwolKSS5ZcQcrQNnwI90TDAzmtFYwWg==
+  version "0.14.7"
+  resolved "https://registry.yarnpkg.com/regenerator/-/regenerator-0.14.7.tgz#72ddf334269d7929b9f99ea1fd46853ffa4215bf"
+  integrity sha512-UK9DqORI8wtNG5nXAKeDcqae1ZYDRzjMpEfS8LFiXxFzcaGL3e+3RFgLet0qLPU9OKUfVK5T49VQ0iBpg9VYQg==
   dependencies:
-    "@babel/core" "^7.4.4"
-    "@babel/runtime" "^7.4.4"
-    "@babel/types" "^7.4.4"
+    "@babel/core" "^7.8.4"
+    "@babel/runtime" "^7.8.4"
+    "@babel/types" "^7.8.3"
     commoner "^0.10.8"
     private "^0.1.8"
-    recast "^0.17.6"
-    regenerator-preset "^0.13.0"
-    regenerator-runtime "^0.13.2"
-    regenerator-transform "^0.14.0"
+    recast "^0.18.7"
+    regenerator-preset "^0.13.2"
+    regenerator-runtime "^0.13.7"
+    regenerator-transform "^0.14.5"
     through "^2.3.8"
 
 regex-cache@^0.4.2:
@@ -11770,12 +11569,13 @@ regex-cache@^0.4.2:
   dependencies:
     is-equal-shallow "^0.1.3"
 
-regex-not@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9"
-  integrity sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=
+regex-not@^1.0.0, regex-not@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
+  integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==
   dependencies:
-    extend-shallow "^2.0.1"
+    extend-shallow "^3.0.2"
+    safe-regex "^1.1.0"
 
 regexp-to-ast@0.4.0:
   version "0.4.0"
@@ -11791,14 +11591,13 @@ regexp.escape@^1.1.0:
     es-abstract "^1.17.0"
     function-bind "^1.1.1"
 
-regexpu-core@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
-  integrity sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=
+regexp.prototype.flags@^1.2.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
+  integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
   dependencies:
-    regenerate "^1.2.1"
-    regjsgen "^0.2.0"
-    regjsparser "^0.1.4"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
 
 regexpu-core@^2.0.0:
   version "2.0.0"
@@ -11809,27 +11608,27 @@ regexpu-core@^2.0.0:
     regjsgen "^0.2.0"
     regjsparser "^0.1.4"
 
-regexpu-core@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
-  integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==
+regexpu-core@^4.7.1:
+  version "4.7.1"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+  integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
   dependencies:
     regenerate "^1.4.0"
-    regenerate-unicode-properties "^8.1.0"
-    regjsgen "^0.5.0"
-    regjsparser "^0.6.0"
+    regenerate-unicode-properties "^8.2.0"
+    regjsgen "^0.5.1"
+    regjsparser "^0.6.4"
     unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.1.0"
+    unicode-match-property-value-ecmascript "^1.2.0"
 
 regjsgen@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
   integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=
 
-regjsgen@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
-  integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
+regjsgen@^0.5.1:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+  integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
 
 regjsparser@^0.1.4:
   version "0.1.5"
@@ -11838,10 +11637,10 @@ regjsparser@^0.1.4:
   dependencies:
     jsesc "~0.5.0"
 
-regjsparser@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c"
-  integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==
+regjsparser@^0.6.4:
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272"
+  integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==
   dependencies:
     jsesc "~0.5.0"
 
@@ -11961,20 +11760,20 @@ remove-trailing-separator@^1.0.1:
   integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
 
 renderkid@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319"
-  integrity sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149"
+  integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==
   dependencies:
     css-select "^1.1.0"
-    dom-converter "~0.1"
-    htmlparser2 "~3.3.0"
+    dom-converter "^0.2"
+    htmlparser2 "^3.3.0"
     strip-ansi "^3.0.0"
-    utila "~0.3"
+    utila "^0.4.0"
 
 repeat-element@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
-  integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+  integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
 
 repeat-string@^1.5.0, repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1:
   version "1.6.1"
@@ -11993,11 +11792,16 @@ replace-ext@0.0.1:
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
   integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=
 
-replace-ext@1.0.0, replace-ext@^1.0.0:
+replace-ext@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
   integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
 
+replace-ext@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a"
+  integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==
+
 request-progress@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe"
@@ -12005,34 +11809,6 @@ request-progress@3.0.0:
   dependencies:
     throttleit "^1.0.0"
 
-request@2.81.0:
-  version "2.81.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
-  integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=
-  dependencies:
-    aws-sign2 "~0.6.0"
-    aws4 "^1.2.1"
-    caseless "~0.12.0"
-    combined-stream "~1.0.5"
-    extend "~3.0.0"
-    forever-agent "~0.6.1"
-    form-data "~2.1.1"
-    har-validator "~4.2.1"
-    hawk "~3.1.3"
-    http-signature "~1.1.0"
-    is-typedarray "~1.0.0"
-    isstream "~0.1.2"
-    json-stringify-safe "~5.0.1"
-    mime-types "~2.1.7"
-    oauth-sign "~0.8.1"
-    performance-now "^0.2.0"
-    qs "~6.4.0"
-    safe-buffer "^5.0.1"
-    stringstream "~0.0.4"
-    tough-cookie "~2.3.0"
-    tunnel-agent "^0.6.0"
-    uuid "^3.0.0"
-
 request@2.88.0:
   version "2.88.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
@@ -12086,32 +11862,30 @@ request@2.88.0:
     tunnel-agent "~0.4.1"
 
 request@^2.79.0:
-  version "2.85.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
-  integrity sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==
+  version "2.88.2"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+  integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
   dependencies:
     aws-sign2 "~0.7.0"
-    aws4 "^1.6.0"
+    aws4 "^1.8.0"
     caseless "~0.12.0"
-    combined-stream "~1.0.5"
-    extend "~3.0.1"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
     forever-agent "~0.6.1"
-    form-data "~2.3.1"
-    har-validator "~5.0.3"
-    hawk "~6.0.2"
+    form-data "~2.3.2"
+    har-validator "~5.1.3"
     http-signature "~1.2.0"
     is-typedarray "~1.0.0"
     isstream "~0.1.2"
     json-stringify-safe "~5.0.1"
-    mime-types "~2.1.17"
-    oauth-sign "~0.8.2"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
     performance-now "^2.1.0"
-    qs "~6.5.1"
-    safe-buffer "^5.1.1"
-    stringstream "~0.0.5"
-    tough-cookie "~2.3.3"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.5.0"
     tunnel-agent "^0.6.0"
-    uuid "^3.1.0"
+    uuid "^3.3.2"
 
 require-directory@^2.1.1:
   version "2.1.1"
@@ -12128,6 +11902,11 @@ require-main-filename@^1.0.1:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
   integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
 
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
 require-uncached@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
@@ -12136,7 +11915,7 @@ require-uncached@^1.0.2:
     caller-path "^0.1.0"
     resolve-from "^1.0.0"
 
-requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0:
+requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
@@ -12183,14 +11962,7 @@ resolve@1.1.7:
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.1.3, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.2:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
-  integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
-  dependencies:
-    path-parse "^1.0.6"
-
-resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0:
+resolve@^1.1.3, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.2.0, resolve@^1.3.2:
   version "1.17.0"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
   integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
@@ -12232,31 +12004,26 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
-  version "2.6.2"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
-  integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
-  dependencies:
-    glob "^7.0.5"
-
-rimraf@^2.6.3:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
   dependencies:
     glob "^7.1.3"
 
-rimraf@~2.2.6:
-  version "2.2.8"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
-  integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=
+rimraf@~2.6.2:
+  version "2.6.3"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
+  integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
+  dependencies:
+    glob "^7.1.3"
 
 ripemd160@^2.0.0, ripemd160@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
-  integrity sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
+  integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
   dependencies:
-    hash-base "^2.0.0"
+    hash-base "^3.0.0"
     inherits "^2.0.1"
 
 rst-selector-parser@^2.2.3:
@@ -12292,28 +12059,35 @@ rx@2.3.24:
   integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=
 
 rxjs@^5.0.0-beta.11:
-  version "5.5.6"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
-  integrity sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==
+  version "5.5.12"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
+  integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==
   dependencies:
     symbol-observable "1.0.1"
 
-safe-buffer@5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
-  integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==
-
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
+safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
 safe-json-parse@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
   integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=
 
-"safer-buffer@>= 2.1.2 < 3":
+safe-regex@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
+  integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4=
+  dependencies:
+    ret "~0.1.10"
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -12332,13 +12106,13 @@ sane@~1.5.0:
     watch "~0.10.0"
 
 sauce-connect-launcher@^1.1.1:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.2.3.tgz#d2f931ad7ae8fdabf1968a440e7b20417aca7f86"
-  integrity sha1-0vkxrXro/avxlopEDnsgQXrKf4Y=
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0"
+  integrity sha512-wf0coUlidJ7rmeClgVVBh6Kw55/yalZCY/Un5RgjSnTXRAeGqagnTsTYpZaqC4dCtrY4myuYpOAZXCdbO7lHfQ==
   dependencies:
     adm-zip "~0.4.3"
     async "^2.1.2"
-    https-proxy-agent "~1.0.0"
+    https-proxy-agent "^5.0.0"
     lodash "^4.16.6"
     rimraf "^2.5.4"
 
@@ -12354,13 +12128,22 @@ schema-utils@^0.3.0:
   dependencies:
     ajv "^5.0.0"
 
-schema-utils@^0.4.2:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.3.tgz#e2a594d3395834d5e15da22b48be13517859458e"
-  integrity sha512-sgv/iF/T4/SewJkaVpldKC4WjSkz0JsOh2eKtxCPpCO1oR05+7MOF+H476HVRbLArkgA7j5TRJJ4p2jdFkUGQQ==
+schema-utils@^0.4.0, schema-utils@^0.4.5:
+  version "0.4.7"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
+  integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
   dependencies:
-    ajv "^5.0.0"
-    ajv-keywords "^2.1.0"
+    ajv "^6.1.0"
+    ajv-keywords "^3.1.0"
+
+schema-utils@^2.6.5:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
+  integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
+  dependencies:
+    "@types/json-schema" "^7.0.5"
+    ajv "^6.12.4"
+    ajv-keywords "^3.5.2"
 
 screenfull@^4.2.1:
   version "4.2.1"
@@ -12373,52 +12156,52 @@ select-hose@^2.0.0:
   integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
 
 selfsigned@^1.9.1:
-  version "1.10.1"
-  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52"
-  integrity sha1-v4y3uDJWxFUeMTR8YxF3jbme7FI=
+  version "1.10.8"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30"
+  integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==
   dependencies:
-    node-forge "0.6.33"
+    node-forge "^0.10.0"
 
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
-  integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@^6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+  integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
 
 semver@~5.0.1:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
   integrity sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=
 
-send@0.16.1:
-  version "0.16.1"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
-  integrity sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==
+send@0.17.1:
+  version "0.17.1"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
+  integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
   dependencies:
     debug "2.6.9"
-    depd "~1.1.1"
+    depd "~1.1.2"
     destroy "~1.0.4"
-    encodeurl "~1.0.1"
+    encodeurl "~1.0.2"
     escape-html "~1.0.3"
     etag "~1.8.1"
     fresh "0.5.2"
-    http-errors "~1.6.2"
-    mime "1.4.1"
-    ms "2.0.0"
+    http-errors "~1.7.2"
+    mime "1.6.0"
+    ms "2.1.1"
     on-finished "~2.3.0"
-    range-parser "~1.2.0"
-    statuses "~1.3.1"
+    range-parser "~1.2.1"
+    statuses "~1.5.0"
 
 serialize-javascript@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
-  integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
+  integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
 
-serve-index@^1.7.2:
+serve-index@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
   integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=
@@ -12431,47 +12214,25 @@ serve-index@^1.7.2:
     mime-types "~2.1.17"
     parseurl "~1.3.2"
 
-serve-static@1.13.1:
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
-  integrity sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==
+serve-static@1.14.1:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
+  integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
   dependencies:
-    encodeurl "~1.0.1"
+    encodeurl "~1.0.2"
     escape-html "~1.0.3"
-    parseurl "~1.3.2"
-    send "0.16.1"
+    parseurl "~1.3.3"
+    send "0.17.1"
 
-set-blocking@^2.0.0, set-blocking@~2.0.0:
+set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
 
-set-getter@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
-  integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=
-  dependencies:
-    to-object-path "^0.3.0"
-
-set-immediate-shim@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
-  integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
-
-set-value@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
-  integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE=
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-extendable "^0.1.1"
-    is-plain-object "^2.0.1"
-    to-object-path "^0.3.0"
-
-set-value@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
-  integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==
+set-value@^2.0.0, set-value@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
+  integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
   dependencies:
     extend-shallow "^2.0.1"
     is-extendable "^0.1.1"
@@ -12483,28 +12244,28 @@ setimmediate@^1.0.4, setimmediate@^1.0.5:
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
   integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
 
-setprototypeof@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
-  integrity sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=
-
 setprototypeof@1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
   integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
 
+setprototypeof@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
+  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+
 sha.js@^2.4.0, sha.js@^2.4.8:
-  version "2.4.9"
-  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d"
-  integrity sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==
+  version "2.4.11"
+  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
+  integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
   dependencies:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
 shallowequal@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.0.2.tgz#1561dbdefb8c01408100319085764da3fcf83f8f"
-  integrity sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+  integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
 
 shebang-command@^1.2.0:
   version "1.2.0"
@@ -12533,9 +12294,9 @@ shellwords@^0.1.1:
   integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
 
 signal-exit@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-  integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+  integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
 simple-statistics@^3.0.0:
   version "3.0.0"
@@ -12581,9 +12342,9 @@ snapdragon-util@^3.0.1:
     kind-of "^3.2.0"
 
 snapdragon@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370"
-  integrity sha1-4StUh/re0+PeoKyR6UAL91tAE3A=
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
+  integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
   dependencies:
     base "^0.11.1"
     debug "^2.2.0"
@@ -12592,7 +12353,7 @@ snapdragon@^0.8.1:
     map-cache "^0.2.2"
     source-map "^0.5.6"
     source-map-resolve "^0.5.0"
-    use "^2.0.0"
+    use "^3.1.0"
 
 sntp@1.x.x:
   version "1.0.9"
@@ -12601,17 +12362,10 @@ sntp@1.x.x:
   dependencies:
     hoek "2.x.x"
 
-sntp@2.x.x:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
-  integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==
-  dependencies:
-    hoek "4.x.x"
-
-sockjs-client@1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
-  integrity sha1-W6vjhrd15M8U51IJEUUmVAFsixI=
+sockjs-client@1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83"
+  integrity sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=
   dependencies:
     debug "^2.6.6"
     eventsource "0.1.6"
@@ -12636,16 +12390,16 @@ sort-keys@^1.0.0:
     is-plain-obj "^1.0.0"
 
 source-list-map@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
-  integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
+  integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
 
 source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
-  integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
+  integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
   dependencies:
-    atob "^2.1.1"
+    atob "^2.1.2"
     decode-uri-component "^0.2.0"
     resolve-url "^0.2.1"
     source-map-url "^0.4.0"
@@ -12663,7 +12417,7 @@ source-map-url@^0.4.0:
   resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
   integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
 
-source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
+source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -12675,64 +12429,69 @@ source-map@^0.4.4, source-map@~0.4.2:
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.6.1, source-map@~0.6.1:
+source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
 space-separated-tokens@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.1.tgz#9695b9df9e65aec1811d4c3f9ce52520bc2f7e4d"
-  integrity sha1-lpW5355lrsGBHUw/nOUlILwvfk0=
-  dependencies:
-    trim "0.0.1"
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
+  integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
 
 spawn-command@^0.0.2-1:
   version "0.0.2-1"
   resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
   integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
 
-spdx-correct@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
-  integrity sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=
+spdx-correct@^3.0.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
+  integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
   dependencies:
-    spdx-license-ids "^1.0.2"
+    spdx-expression-parse "^3.0.0"
+    spdx-license-ids "^3.0.0"
 
-spdx-expression-parse@~1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
-  integrity sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=
+spdx-exceptions@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
 
-spdx-license-ids@^1.0.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
-  integrity sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=
+spdx-expression-parse@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
+  dependencies:
+    spdx-exceptions "^2.1.0"
+    spdx-license-ids "^3.0.0"
+
+spdx-license-ids@^3.0.0:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
+  integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==
 
-spdy-transport@^2.0.18:
-  version "2.0.20"
-  resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d"
-  integrity sha1-c15yBUxIayNU/onnAiVgBKOazk0=
+spdy-transport@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
+  integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
   dependencies:
-    debug "^2.6.8"
-    detect-node "^2.0.3"
+    debug "^4.1.0"
+    detect-node "^2.0.4"
     hpack.js "^2.1.6"
-    obuf "^1.1.1"
-    readable-stream "^2.2.9"
-    safe-buffer "^5.0.1"
-    wbuf "^1.7.2"
+    obuf "^1.1.2"
+    readable-stream "^3.0.6"
+    wbuf "^1.7.3"
 
-spdy@^3.4.1:
-  version "3.4.7"
-  resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc"
-  integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=
+spdy@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b"
+  integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==
   dependencies:
-    debug "^2.6.8"
-    handle-thing "^1.2.5"
+    debug "^4.1.0"
+    handle-thing "^2.0.0"
     http-deceiver "^1.2.7"
-    safe-buffer "^5.0.1"
     select-hose "^2.0.0"
-    spdy-transport "^2.0.18"
+    spdy-transport "^3.0.0"
 
 split-string@^3.0.1, split-string@^3.0.2:
   version "3.1.0"
@@ -12747,18 +12506,18 @@ sprintf-js@~1.0.2:
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
 sshpk@^1.7.0:
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
-  integrity sha1-US322mKHFEMW3EwY/hzx2UBzm+M=
+  version "1.16.1"
+  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
+  integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
   dependencies:
     asn1 "~0.2.3"
     assert-plus "^1.0.0"
-    dashdash "^1.12.0"
-    getpass "^0.1.1"
-  optionalDependencies:
     bcrypt-pbkdf "^1.0.0"
+    dashdash "^1.12.0"
     ecc-jsbn "~0.1.1"
+    getpass "^0.1.1"
     jsbn "~0.1.0"
+    safer-buffer "^2.0.2"
     tweetnacl "~0.14.0"
 
 ssri@^5.2.4:
@@ -12787,9 +12546,9 @@ staged-git-files@0.0.4:
   integrity sha1-15fhtVHKemOd7AI33G60u5vhfTU=
 
 state-toggle@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.2.tgz#75e93a61944116b4959d665c8db2d243631d6ddc"
-  integrity sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe"
+  integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==
 
 static-extend@^0.1.1:
   version "0.1.2"
@@ -12799,15 +12558,10 @@ static-extend@^0.1.1:
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.3.1 < 2":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
-  integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
-
-statuses@~1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
-  integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=
+"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+  integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
 
 stream-array@^1.1.0:
   version "1.1.2"
@@ -12817,9 +12571,9 @@ stream-array@^1.1.0:
     readable-stream "~2.1.0"
 
 stream-browserify@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
-  integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
+  integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
   dependencies:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
@@ -12833,28 +12587,28 @@ stream-combiner2@^1.1.1:
     readable-stream "^2.0.2"
 
 stream-each@^1.1.0:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
-  integrity sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
+  integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==
   dependencies:
     end-of-stream "^1.1.0"
     stream-shift "^1.0.0"
 
 stream-http@^2.7.2:
-  version "2.7.2"
-  resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
-  integrity sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==
+  version "2.8.3"
+  resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
+  integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==
   dependencies:
     builtin-status-codes "^3.0.0"
     inherits "^2.0.1"
-    readable-stream "^2.2.6"
+    readable-stream "^2.3.6"
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
 stream-shift@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
-  integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
+  integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
 
 stream-to-observable@^0.1.0:
   version "0.1.0"
@@ -12895,14 +12649,22 @@ string-width@^2.0.0:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
-string.prototype.trim@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
-  integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=
+string-width@^4.1.0, string-width@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
+  integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.0"
+
+string.prototype.trim@^1.2.1:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz#f538d0bacd98fc4297f0bef645226d5aaebf59f3"
+  integrity sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg==
   dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.5.0"
-    function-bind "^1.0.2"
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.0"
 
 string.prototype.trimend@^1.0.1:
   version "1.0.1"
@@ -12912,22 +12674,6 @@ string.prototype.trimend@^1.0.1:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
 
-string.prototype.trimleft@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
-  integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==
-  dependencies:
-    define-properties "^1.1.3"
-    function-bind "^1.1.1"
-
-string.prototype.trimright@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9"
-  integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==
-  dependencies:
-    define-properties "^1.1.3"
-    function-bind "^1.1.1"
-
 string.prototype.trimstart@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
@@ -12942,11 +12688,11 @@ string_decoder@0.10, string_decoder@~0.10.x:
   integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
 
 string_decoder@^1.0.0, string_decoder@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
-  integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+  integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
   dependencies:
-    safe-buffer "~5.1.0"
+    safe-buffer "~5.2.0"
 
 string_decoder@~1.1.1:
   version "1.1.1"
@@ -12956,35 +12702,28 @@ string_decoder@~1.1.1:
     safe-buffer "~5.1.0"
 
 stringify-entities@^1.0.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.1.tgz#b150ec2d72ac4c1b5f324b51fb6b28c9cdff058c"
-  integrity sha1-sVDsLXKsTBtfMktR+2soyc3/BYw=
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7"
+  integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==
   dependencies:
     character-entities-html4 "^1.0.0"
     character-entities-legacy "^1.0.0"
     is-alphanumerical "^1.0.0"
     is-hexadecimal "^1.0.0"
 
-stringify-object@3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.1.tgz#2720c2eff940854c819f6ee252aaeb581f30624d"
-  integrity sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==
+stringify-object@3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd"
+  integrity sha512-O696NF21oLiDy8PhpWu8AEqoZHw++QW6mUv0UvKZe8gWSdSvMXkiLufK7OmnP27Dro4GU5kb9U7JIO0mBuCRQg==
   dependencies:
     get-own-enumerable-property-symbols "^2.0.1"
     is-obj "^1.0.1"
     is-regexp "^1.0.0"
 
-stringstream@~0.0.4, stringstream@~0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
-  integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=
-
-strip-ansi@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220"
-  integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=
-  dependencies:
-    ansi-regex "^0.2.1"
+stringstream@~0.0.4:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
+  integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==
 
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
@@ -13000,6 +12739,13 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
+strip-ansi@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
+  integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
+  dependencies:
+    ansi-regex "^5.0.0"
+
 strip-ansi@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
@@ -13073,7 +12819,7 @@ styled-components@3.2.6, styled-components@>=3.0.0:
     stylis-rule-sheet "^0.0.10"
     supports-color "^3.2.3"
 
-styled-system@2.2.5, "styled-system@>=1.1 || >=2.0", styled-system@>=2.0.1, styled-system@>=2.0.2:
+styled-system@2.2.5, "styled-system@>=2.0.0 || >=3.0.0", styled-system@>=2.0.1, styled-system@>=2.0.2:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-2.2.5.tgz#95f1e2c2c9ddc5c462cc56237cf039aa9ecfd27d"
   integrity sha512-/KJxEzd+mPQxTdr85l7K3b6ac97R4G0M2SlTm9sM48dGkg1CjTIURIvvSV3Y92kE+9GUfrPsG2MpeV8QnmSIQg==
@@ -13097,18 +12843,13 @@ subarg@^1.0.0:
   dependencies:
     minimist "^1.1.0"
 
-supports-color@5.5.0, supports-color@^5.1.0, supports-color@^5.3.0:
+supports-color@5.5.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
   dependencies:
     has-flag "^3.0.0"
 
-supports-color@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
-  integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=
-
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -13159,9 +12900,9 @@ symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.2.0:
   integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
 
 symbol-tree@^3.2.1:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
-  integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+  integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
 system-components@2.0.3:
   version "2.0.3"
@@ -13190,45 +12931,21 @@ tapable@^0.1.8:
   integrity sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=
 
 tapable@^0.2.7:
-  version "0.2.8"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
-  integrity sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=
-
-tar-pack@^3.4.0:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
-  integrity sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==
-  dependencies:
-    debug "^2.2.0"
-    fstream "^1.0.10"
-    fstream-ignore "^1.0.5"
-    once "^1.3.3"
-    readable-stream "^2.1.4"
-    rimraf "^2.5.1"
-    tar "^2.2.1"
-    uid-number "^0.0.6"
-
-tar@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
-  integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
-  dependencies:
-    block-stream "*"
-    fstream "^1.0.2"
-    inherits "2"
+  version "0.2.9"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.9.tgz#af2d8bbc9b04f74ee17af2b4d9048f807acd18a8"
+  integrity sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==
 
 temp@^0.8.1:
-  version "0.8.3"
-  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
-  integrity sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=
+  version "0.8.4"
+  resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2"
+  integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==
   dependencies:
-    os-tmpdir "^1.0.0"
-    rimraf "~2.2.6"
+    rimraf "~2.6.2"
 
-test-exclude@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26"
-  integrity sha512-35+Asrsk3XHJDBgf/VRFexPgh3UyETv8IAn/LRTiZjVy6rjPVqdEk8dJcJYBzl1w0XCJM48lvTy8SfEsCWS4nA==
+test-exclude@^4.2.1:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
+  integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==
   dependencies:
     arrify "^1.0.1"
     micromatch "^2.3.11"
@@ -13237,9 +12954,9 @@ test-exclude@^4.1.1:
     require-main-filename "^1.0.1"
 
 tether@^1.2.0:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.3.tgz#fd547024c47b6e5c9b87e1880f997991a9a6ad54"
-  integrity sha512-YCfE/Ym9MpZpzUmzbek7MiLEyTofxx2YS0rJfSOUXX0aZtfQgxcgw7/Re2oGJUsREWZtEF0DzBKCjqH+DzgL6A==
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.7.tgz#d56a818590d8fe72e387f77a67f93ab96d8e1fb2"
+  integrity sha512-Z0J1aExjoFU8pybVkQAo/vD2wfSO63r+XOPfWQMC5qtf1bI7IWqNk4MiyBcgvvnY8kqnY06dVdvwTK2S3PU/Fw==
 
 text-table@~0.2.0:
   version "0.2.0"
@@ -13254,9 +12971,9 @@ thenify-all@^1.0.0, thenify-all@^1.6.0:
     thenify ">= 3.1.0 < 4"
 
 "thenify@>= 3.1.0 < 4":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
-  integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
   dependencies:
     any-promise "^1.0.0"
 
@@ -13278,6 +12995,14 @@ through2-filter@^2.0.0:
     through2 "~2.0.0"
     xtend "~4.0.0"
 
+through2-filter@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
+  integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
+  dependencies:
+    through2 "~2.0.0"
+    xtend "~4.0.0"
+
 through2@^0.6.0:
   version "0.6.5"
   resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
@@ -13286,12 +13011,12 @@ through2@^0.6.0:
     readable-stream ">=1.0.33-1 <1.1.0-0"
     xtend ">=4.0.0 <4.1.0-0"
 
-through2@^2.0.0, through2@^2.0.1, through2@~2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
-  integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=
+through2@^2.0.0, through2@~2.0.0:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+  integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
   dependencies:
-    readable-stream "^2.1.5"
+    readable-stream "~2.3.6"
     xtend "~4.0.1"
 
 "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8:
@@ -13299,39 +13024,39 @@ through2@^2.0.0, through2@^2.0.1, through2@~2.0.0:
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
 
-thunky@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
-  integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
+thunky@^1.0.2:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
+  integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
 
 time-stamp@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
-  integrity sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.2.0.tgz#917e0a66905688790ec7bbbde04046259af83f57"
+  integrity sha512-zxke8goJQpBeEgD82CXABeMh0LSJcj7CXEd0OHOg45HgcofF7pxNwZm9+RknpxpDhwN4gFpySkApKfFYfRQnUA==
 
 timers-browserify@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6"
-  integrity sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==
+  version "2.0.11"
+  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"
+  integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==
   dependencies:
     setimmediate "^1.0.4"
 
 tiny-lr@^1.0.3:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.0.5.tgz#21f40bf84ebd1f853056680375eef1670c334112"
-  integrity sha512-YrxUSiMgOVh3PnAqtdAUQuUVEVRnqcRCxJ3BHrl/aaWV2fplKKB60oClM0GH2Gio2hcXvkxMUxsC/vXZrQePlg==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"
+  integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==
   dependencies:
     body "^5.1.0"
-    debug "~2.6.7"
+    debug "^3.1.0"
     faye-websocket "~0.10.0"
-    livereload-js "^2.2.2"
+    livereload-js "^2.3.0"
     object-assign "^4.1.0"
     qs "^6.4.0"
 
 tinycolor2@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
-  integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
+  integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
 
 tmp@0.0.23:
   version "0.0.23"
@@ -13362,6 +13087,13 @@ to-arraybuffer@^1.0.0:
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
   integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
 
+to-camel-case@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46"
+  integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY=
+  dependencies:
+    to-space-case "^1.0.0"
+
 to-fast-properties@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
@@ -13372,6 +13104,11 @@ to-fast-properties@^2.0.0:
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
   integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
 
+to-no-case@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a"
+  integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=
+
 to-object-path@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -13387,20 +13124,40 @@ to-regex-range@^2.1.0:
     is-number "^3.0.0"
     repeat-string "^1.6.1"
 
-to-regex@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae"
-  integrity sha1-FTWL7kosg712N3uh3ASdDxiDeq4=
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
   dependencies:
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    regex-not "^1.0.0"
+    is-number "^7.0.0"
+
+to-regex@^3.0.1, to-regex@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
+  integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
+  dependencies:
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
+    regex-not "^1.0.2"
+    safe-regex "^1.1.0"
+
+to-space-case@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17"
+  integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=
+  dependencies:
+    to-no-case "^1.0.0"
 
-toggle-selection@^1.0.3:
+toggle-selection@^1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
   integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
 
+toidentifier@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+
 topo@1.x.x:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
@@ -13409,14 +13166,22 @@ topo@1.x.x:
     hoek "2.x.x"
 
 toposort@^1.0.0:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec"
-  integrity sha1-wxdI5V0hDv/AD9zcfW5o19e7nOw=
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
+  integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
 
-tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
-  integrity sha1-C2GKVWW23qkL80JdBNVe3EdadWE=
+tough-cookie@^2.3.2, tough-cookie@~2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+  integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+  dependencies:
+    psl "^1.1.28"
+    punycode "^2.1.1"
+
+tough-cookie@~2.3.0:
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
+  integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==
   dependencies:
     punycode "^1.4.1"
 
@@ -13434,14 +13199,14 @@ tr46@~0.0.3:
   integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
 
 tree-kill@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
-  integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
+  integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
 
 trim-lines@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.0.tgz#9926d03ede13ba18f7d42222631fb04c79ff26fe"
-  integrity sha1-mSbQPt4Tuhj31CIiYx+wTHn/Jv4=
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115"
+  integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==
 
 trim-newlines@^1.0.0:
   version "1.0.0"
@@ -13454,9 +13219,9 @@ trim-right@^1.0.1:
   integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
 
 trim-trailing-lines@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz#d2f1e153161152e9f02fabc670fb40bec2ea2e3a"
-  integrity sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz#7f0739881ff76657b7776e10874128004b625a94"
+  integrity sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==
 
 trim@0.0.1:
   version "0.0.1"
@@ -13464,9 +13229,9 @@ trim@0.0.1:
   integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0=
 
 trough@^1.0.0:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e"
-  integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
+  integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
 
 tsconfig-paths@^3.9.0:
   version "3.9.0"
@@ -13478,7 +13243,7 @@ tsconfig-paths@^3.9.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
-ttag@^1.7.8:
+ttag@1.7.15:
   version "1.7.15"
   resolved "https://registry.yarnpkg.com/ttag/-/ttag-1.7.15.tgz#e0a97283071bc945b1cde09a7cd3b82a0d0921a6"
   integrity sha512-2DKhWZ1Qg2Yn0FHsB21Ov5ymBdaxlTeiDu7bBO6r/ziCW993OxaJyW6MrKhD4zCOCP4P5W295dGPSfReQ1WiBg==
@@ -13515,41 +13280,51 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
-type-is@~1.6.15:
-  version "1.6.15"
-  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
-  integrity sha1-yrEPtJCeRByChC6v4a1kbIGARBA=
+type-is@~1.6.17, type-is@~1.6.18:
+  version "1.6.18"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
   dependencies:
     media-typer "0.3.0"
-    mime-types "~2.1.15"
+    mime-types "~2.1.24"
+
+type@^1.0.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
+  integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
+
+type@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f"
+  integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==
 
 typedarray@^0.0.6, typedarray@~0.0.5:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-ua-parser-js@^0.7.9:
-  version "0.7.17"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
-  integrity sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==
+ua-parser-js@^0.7.18:
+  version "0.7.22"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
+  integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==
 
 uglify-es@^3.3.4:
-  version "3.3.5"
-  resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.5.tgz#cf7e695da81999f85196b15e2978862f13212f88"
-  integrity sha512-7IvaFuYtfbcXm0fGb13mmRYVQdzQDXETAtvYHbCDPt2V88Y8l2HaULOyW6ueoYA0JhGIcLK7dtHkDcBWySqnBw==
+  version "3.3.9"
+  resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
+  integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==
   dependencies:
-    commander "~2.12.1"
+    commander "~2.13.0"
     source-map "~0.6.1"
 
-uglify-js@3.3.x:
-  version "3.3.5"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.5.tgz#4c4143dfe08e8825746675cc49a6874a933b543e"
-  integrity sha512-ZebM2kgBL/UI9rKeAbsS2J0UPPv7SBy5hJNZml/YxB1zC6JK8IztcPs+cxilE4pu0li6vadVSFqiO7xFTKuSrg==
+uglify-js@3.4.x:
+  version "3.4.10"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
+  integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
   dependencies:
-    commander "~2.12.1"
+    commander "~2.19.0"
     source-map "~0.6.1"
 
-uglify-js@^2.6, uglify-js@^2.8.29:
+uglify-js@^2.8.29:
   version "2.8.29"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
   integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0=
@@ -13559,6 +13334,11 @@ uglify-js@^2.6, uglify-js@^2.8.29:
   optionalDependencies:
     uglify-to-browserify "~1.0.0"
 
+uglify-js@^3.1.4:
+  version "3.11.2"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.11.2.tgz#9f50325544273c27b20e586def140e7726c525ea"
+  integrity sha512-G440NU6fewtnQftSgqRV1r2A5ChKbU1gqFCJ7I8S7MPpY/eZZfLGefaY6gUZYiWebMaO+txgiQ1ZyLDuNWJulg==
+
 uglify-to-browserify@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@@ -13574,24 +13354,19 @@ uglifyjs-webpack-plugin@^0.4.6:
     webpack-sources "^1.0.1"
 
 uglifyjs-webpack-plugin@^1.0.0:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.1.6.tgz#f4ba8449edcf17835c18ba6ae99b9d610857fb19"
-  integrity sha512-VUja+7rYbznEvUaeb8IxOCTUrq4BCb1ml0vffa+mfwKtrAwlqnU0ENF14DtYltV1cxd/HSuK51CCA/D/8kMQVw==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de"
+  integrity sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==
   dependencies:
-    cacache "^10.0.1"
+    cacache "^10.0.4"
     find-cache-dir "^1.0.0"
-    schema-utils "^0.4.2"
+    schema-utils "^0.4.5"
     serialize-javascript "^1.4.0"
     source-map "^0.6.1"
     uglify-es "^3.3.4"
     webpack-sources "^1.1.0"
     worker-farm "^1.5.2"
 
-uid-number@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
-  integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
-
 ultron@1.0.x:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
@@ -13603,9 +13378,9 @@ unc-path-regex@^0.1.0, unc-path-regex@^0.1.2:
   integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
 
 underscore@^1.8.3:
-  version "1.8.3"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
-  integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.11.0.tgz#dd7c23a195db34267186044649870ff1bab5929e"
+  integrity sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw==
 
 underscore@~1.6.0:
   version "1.6.0"
@@ -13613,12 +13388,12 @@ underscore@~1.6.0:
   integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
 
 unherit@^1.0.4:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
-  integrity sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"
+  integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==
   dependencies:
-    inherits "^2.0.1"
-    xtend "^4.0.1"
+    inherits "^2.0.0"
+    xtend "^4.0.0"
 
 unicode-canonical-property-names-ecmascript@^1.0.4:
   version "1.0.4"
@@ -13633,30 +13408,17 @@ unicode-match-property-ecmascript@^1.0.4:
     unicode-canonical-property-names-ecmascript "^1.0.4"
     unicode-property-aliases-ecmascript "^1.0.4"
 
-unicode-match-property-value-ecmascript@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
-  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
+unicode-match-property-value-ecmascript@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
+  integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
 
 unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
-  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
-
-unified@^6.0.0:
-  version "6.1.6"
-  resolved "https://registry.yarnpkg.com/unified/-/unified-6.1.6.tgz#5ea7f807a0898f1f8acdeefe5f25faa010cc42b1"
-  integrity sha512-pW2f82bCIo2ifuIGYcV12fL96kMMYgw7JKVEgh7ODlrM9rj6vXSY3BV+H6lCcv1ksxynFf582hwWLnA1qRFy4w==
-  dependencies:
-    bail "^1.0.0"
-    extend "^3.0.0"
-    is-plain-obj "^1.1.0"
-    trough "^1.0.0"
-    vfile "^2.0.0"
-    x-is-function "^1.0.4"
-    x-is-string "^0.1.0"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
+  integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
 
-unified@^6.1.5:
+unified@^6.0.0, unified@^6.1.5:
   version "6.2.0"
   resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
   integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==
@@ -13669,72 +13431,60 @@ unified@^6.1.5:
     x-is-string "^0.1.0"
 
 union-value@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
-  integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
+  integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
   dependencies:
     arr-union "^3.1.0"
     get-value "^2.0.6"
     is-extendable "^0.1.1"
-    set-value "^0.4.3"
+    set-value "^2.0.1"
 
 uniq@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
   integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
 
-uniqid@^4.0.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1"
-  integrity sha1-iSIN32t1GuUrX3JISGNShZa7hME=
-  dependencies:
-    macaddress "^0.2.8"
-
 uniqs@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
   integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI=
 
 unique-filename@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
-  integrity sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+  integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
   dependencies:
     unique-slug "^2.0.0"
 
 unique-slug@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab"
-  integrity sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
+  integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
   dependencies:
     imurmurhash "^0.1.4"
 
 unique-stream@^2.0.2:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"
-  integrity sha1-WqADz76Uxf+GbE59ZouxxNuts2k=
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
+  integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
   dependencies:
-    json-stable-stringify "^1.0.0"
-    through2-filter "^2.0.0"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    through2-filter "^3.0.0"
 
 unist-builder@^1.0.0, unist-builder@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.2.tgz#8c3b9903ef64bcfb117dd7cf6a5d98fc1b3b27b6"
-  integrity sha1-jDuZA+9kvPsRfdfPal2Y/Bs7J7Y=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.4.tgz#e1808aed30bd72adc3607f25afecebef4dd59e17"
+  integrity sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==
   dependencies:
     object-assign "^4.1.0"
 
 unist-util-generated@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.1.tgz#99f16c78959ac854dee7c615c291924c8bf4de7f"
-  integrity sha1-mfFseJWayFTe58YVwpGSTIv03n8=
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.5.tgz#1e903e68467931ebfaea386dae9ea253628acd42"
+  integrity sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==
 
 unist-util-is@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b"
-  integrity sha1-DDEmKeP5YMZukx6BLT2A53AQlHs=
-
-unist-util-is@^2.1.1:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.3.tgz#459182db31f4742fceaea88d429693cbf0043d20"
   integrity sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA==
@@ -13744,17 +13494,10 @@ unist-util-is@^3.0.0:
   resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd"
   integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==
 
-unist-util-modify-children@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.1.tgz#66d7e6a449e6f67220b976ab3cb8b5ebac39e51d"
-  integrity sha1-ZtfmpEnm9nIguXarPLi166w55R0=
-  dependencies:
-    array-iterate "^1.0.0"
-
 unist-util-position@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.0.tgz#e6e1e03eeeb81c5e1afe553e8d4adfbd7c0d8f82"
-  integrity sha1-5uHgPu64HF4a/lU+jUrfvXwNj4I=
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47"
+  integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==
 
 unist-util-remove-position@^1.0.0:
   version "1.1.4"
@@ -13780,14 +13523,7 @@ unist-util-visit-parents@^2.0.0:
   dependencies:
     unist-util-is "^3.0.0"
 
-unist-util-visit@^1.0.0, unist-util-visit@^1.0.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.3.0.tgz#41ca7c82981fd1ce6c762aac397fc24e35711444"
-  integrity sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==
-  dependencies:
-    unist-util-is "^2.1.1"
-
-unist-util-visit@^1.1.0, unist-util-visit@^1.3.0:
+unist-util-visit@^1.0.0, unist-util-visit@^1.0.1, unist-util-visit@^1.1.0, unist-util-visit@^1.3.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"
   integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==
@@ -13826,25 +13562,30 @@ untildify@3.0.3:
   integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==
 
 unused-files-webpack-plugin@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/unused-files-webpack-plugin/-/unused-files-webpack-plugin-3.2.0.tgz#52e6a929024820da0d07d708f364176df5a3cecf"
-  integrity sha512-cm6D9nDFqXerK99suXGY1mXcpUMEEa0iBFF9Omoysb6hiHctx//T8jIDoIEBU5lkocbNebXnJBF3sNXGgBuLnw==
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/unused-files-webpack-plugin/-/unused-files-webpack-plugin-3.4.0.tgz#adc67a3b5549d028818d3119cbf2b5c88aea8670"
+  integrity sha512-cmukKOBdIqaM1pqThY0+jp+mYgCVyzrD8uRbKEucQwIGZcLIRn+gSRiQ7uLjcDd3Zba9NUxVGyYa7lWM4UCGeg==
   dependencies:
     babel-runtime "^7.0.0-beta.3"
     glob-all "^3.1.0"
-    semver "^5.4.1"
+    semver "^5.5.0"
     util.promisify "^1.0.0"
     warning "^3.0.0"
 
+upath@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
+  integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
+
 upper-case@^1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
   integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
 
 uri-js@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
-  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
+  integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==
   dependencies:
     punycode "^2.1.0"
 
@@ -13858,21 +13599,13 @@ url-join@0.0.1:
   resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8"
   integrity sha1-HbSK1CLTQCRpqH99l73r/k+x48g=
 
-url-parse@1.0.x:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
-  integrity sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=
-  dependencies:
-    querystringify "0.0.x"
-    requires-port "1.0.x"
-
-url-parse@^1.1.8:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
-  integrity sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==
+url-parse@^1.1.8, url-parse@^1.4.3:
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
+  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
   dependencies:
-    querystringify "~1.0.0"
-    requires-port "~1.0.0"
+    querystringify "^2.1.1"
+    requires-port "^1.0.0"
 
 url@0.11.0, url@^0.11.0:
   version "0.11.0"
@@ -13882,14 +13615,10 @@ url@0.11.0, url@^0.11.0:
     punycode "1.3.2"
     querystring "0.2.0"
 
-use@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
-  integrity sha1-riig1y+TvyJCKhii43mZMRLeyOg=
-  dependencies:
-    define-property "^0.2.5"
-    isobject "^3.0.0"
-    lazy-cache "^2.0.2"
+use@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
+  integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
 
 user-home@^1.1.1:
   version "1.1.1"
@@ -13909,26 +13638,30 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
 util.promisify@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
-  integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
+  integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
   dependencies:
-    define-properties "^1.1.2"
-    object.getownpropertydescriptors "^2.0.3"
+    define-properties "^1.1.3"
+    es-abstract "^1.17.2"
+    has-symbols "^1.0.1"
+    object.getownpropertydescriptors "^2.1.0"
 
-util@0.10.3, util@^0.10.3:
+util@0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
   dependencies:
     inherits "2.0.1"
 
-utila@~0.3:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/utila/-/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226"
-  integrity sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=
+util@^0.11.0:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
+  integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
+  dependencies:
+    inherits "2.0.3"
 
-utila@~0.4:
+utila@^0.4.0, utila@~0.4:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
   integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
@@ -13938,15 +13671,10 @@ utils-merge@1.0.1:
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 
-uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
-  integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==
-
-uuid@^3.3.2:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
-  integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
+uuid@^3.0.1, uuid@^3.3.2:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
+  integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
 v8flags@^2.1.1:
   version "2.1.1"
@@ -13961,12 +13689,12 @@ vali-date@^1.0.0:
   integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=
 
 validate-npm-package-license@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
-  integrity sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
+  integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
   dependencies:
-    spdx-correct "~1.0.0"
-    spdx-expression-parse "~1.0.0"
+    spdx-correct "^3.0.0"
+    spdx-expression-parse "^3.0.0"
 
 vary@~1.1.2:
   version "1.1.2"
@@ -13974,9 +13702,9 @@ vary@~1.1.2:
   integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
 
 vendors@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
-  integrity sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"
+  integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==
 
 verror@1.10.0:
   version "1.10.0"
@@ -14011,14 +13739,14 @@ vfile-reporter@^4.0.0:
     vfile-statistics "^1.1.0"
 
 vfile-sort@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-2.1.0.tgz#49501c9e8bbe5adff2e9b3a7671ee1b1e20c5210"
-  integrity sha1-SVAcnou+Wt/y6bOnZx7hseIMUhA=
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-2.2.2.tgz#720fe067ce156aba0b411a01bb0dc65596aa1190"
+  integrity sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==
 
 vfile-statistics@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.0.tgz#02104c60fdeed1d11b1f73ad65330b7634b3d895"
-  integrity sha1-AhBMYP3u0dEbH3OtZTMLdjSz2JU=
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.4.tgz#b99fd15ecf0f44ba088cc973425d666cb7a9f245"
+  integrity sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==
 
 vfile@^2.0.0:
   version "2.3.0"
@@ -14068,9 +13796,9 @@ vinyl@^1.0.0:
     replace-ext "0.0.1"
 
 vinyl@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c"
-  integrity sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974"
+  integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==
   dependencies:
     clone "^2.1.1"
     clone-buffer "^1.0.0"
@@ -14079,17 +13807,15 @@ vinyl@^2.0.0:
     remove-trailing-separator "^1.0.1"
     replace-ext "^1.0.0"
 
-vm-browserify@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
-  integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=
-  dependencies:
-    indexof "0.0.1"
+vm-browserify@^1.0.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
+  integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
 
-wait-for-expect@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.1.tgz#ec204a76b0038f17711e575720aaf28505ac7185"
-  integrity sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==
+wait-for-expect@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463"
+  integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==
 
 walker@~1.0.5:
   version "1.0.7"
@@ -14110,19 +13836,28 @@ watch@~0.10.0:
   resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"
   integrity sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw=
 
+watchpack-chokidar2@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"
+  integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==
+  dependencies:
+    chokidar "^2.1.8"
+
 watchpack@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
-  integrity sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b"
+  integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==
   dependencies:
-    async "^2.1.2"
-    chokidar "^1.7.0"
     graceful-fs "^4.1.2"
+    neo-async "^2.5.0"
+  optionalDependencies:
+    chokidar "^3.4.1"
+    watchpack-chokidar2 "^2.0.0"
 
-wbuf@^1.1.0, wbuf@^1.7.2:
-  version "1.7.2"
-  resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
-  integrity sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=
+wbuf@^1.1.0, wbuf@^1.7.3:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
+  integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
   dependencies:
     minimalistic-assert "^1.0.0"
 
@@ -14148,21 +13883,21 @@ webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.12.0:
     time-stamp "^2.0.0"
 
 webpack-dev-server@^2.9.1:
-  version "2.10.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.10.0.tgz#6db9c77c8cf2e2d7ff85c89fb5e4de6f7227be19"
-  integrity sha512-mFq5S5Sg6nbiGXry+nRlaUoaCcl0IH/LVP60kwwJKBT/8IcwK/ZKduOSBK8bsLwRBh1yFoUYJMKfCo6oeP07+g==
+  version "2.11.5"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz#416fbdea0e04eebe44a626e791d5a2eb37fe8c48"
+  integrity sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==
   dependencies:
     ansi-html "0.0.7"
     array-includes "^3.0.3"
     bonjour "^3.5.0"
-    chokidar "^2.0.0"
-    compression "^1.5.2"
+    chokidar "^2.1.2"
+    compression "^1.7.3"
     connect-history-api-fallback "^1.3.0"
     debug "^3.1.0"
     del "^3.0.0"
     express "^4.16.2"
     html-entities "^1.2.0"
-    http-proxy-middleware "~0.17.4"
+    http-proxy-middleware "^0.19.1"
     import-local "^1.0.0"
     internal-ip "1.2.0"
     ip "^1.1.5"
@@ -14171,14 +13906,14 @@ webpack-dev-server@^2.9.1:
     opn "^5.1.0"
     portfinder "^1.0.9"
     selfsigned "^1.9.1"
-    serve-index "^1.7.2"
+    serve-index "^1.9.1"
     sockjs "0.3.19"
-    sockjs-client "1.1.4"
-    spdy "^3.4.1"
-    strip-ansi "^4.0.0"
+    sockjs-client "1.1.5"
+    spdy "^4.0.0"
+    strip-ansi "^3.0.0"
     supports-color "^5.1.0"
     webpack-dev-middleware "1.12.2"
-    yargs "^10.0.3"
+    yargs "6.6.0"
 
 webpack-notifier@^1.8.0:
   version "1.8.0"
@@ -14190,31 +13925,31 @@ webpack-notifier@^1.8.0:
     strip-ansi "^3.0.1"
 
 webpack-postcss-tools@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/webpack-postcss-tools/-/webpack-postcss-tools-1.1.2.tgz#c5aef3236d0b6dbb45dd263add0831a4abb227e4"
-  integrity sha1-xa7zI20LbbtF3SY63QgxpKuyJ+Q=
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/webpack-postcss-tools/-/webpack-postcss-tools-1.1.3.tgz#215a533ac771f83f1baddd7a665a2913b2d9c1a8"
+  integrity sha512-E/OmkCYMhNn06zx0SOq5Q9BjhvS7KVyQkAsDMnu125Sa5oO91OkloIQtR0HAQy66EX4ipfquvpoUkF5rNw6tcQ==
   dependencies:
-    lodash "^3.7.0"
+    lodash "^4.0.0"
     postcss "^4.1.7"
     resolve "^1.1.6"
 
 webpack-sources@^1.0.1, webpack-sources@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
-  integrity sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
+  integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
   dependencies:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
 webpack@^3.8.1:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725"
-  integrity sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==
+  version "3.12.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.12.0.tgz#3f9e34360370602fcf639e97939db486f4ec0d74"
+  integrity sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==
   dependencies:
     acorn "^5.0.0"
     acorn-dynamic-import "^2.0.0"
-    ajv "^5.1.5"
-    ajv-keywords "^2.0.0"
+    ajv "^6.1.0"
+    ajv-keywords "^3.1.0"
     async "^2.1.2"
     enhanced-resolve "^3.4.0"
     escope "^3.6.0"
@@ -14235,29 +13970,30 @@ webpack@^3.8.1:
     yargs "^8.0.2"
 
 websocket-driver@>=0.5.1:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
-  integrity sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=
+  version "0.7.4"
+  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
+  integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
   dependencies:
-    http-parser-js ">=0.4.0"
+    http-parser-js ">=0.5.1"
+    safe-buffer ">=5.1.0"
     websocket-extensions ">=0.1.1"
 
 websocket-extensions@>=0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
-  integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+  integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
 
 whatwg-encoding@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3"
-  integrity sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
+  integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
   dependencies:
-    iconv-lite "0.4.19"
+    iconv-lite "0.4.24"
 
 whatwg-fetch@>=0.10.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
-  integrity sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3"
+  integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==
 
 whatwg-url@^4.3.0:
   version "4.8.0"
@@ -14283,28 +14019,21 @@ which-module@^2.0.0:
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
 which@^1.1.1, which@^1.2.10, which@^1.2.9, which@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
-  integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
   dependencies:
     isexe "^2.0.0"
 
-wide-align@^1.1.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
-  integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==
-  dependencies:
-    string-width "^1.0.2"
-
 window-size@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
   integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
 
 winston@^2.1.1:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee"
-  integrity sha1-gIBQuT1SZh7Z+2wms/DIJnCLCu4=
+  version "2.4.5"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.5.tgz#f2e431d56154c4ea765545fc1003bd340c95b59a"
+  integrity sha512-TWoamHt5yYvsMarGlGEQE59SbJHqGsZV8/lwC+iCcGeAe0vUaOh+Lv6SYM17ouzC/a/LB1/hz/7sxFBtlu1l4A==
   dependencies:
     async "~1.0.0"
     colors "1.0.x"
@@ -14313,25 +14042,25 @@ winston@^2.1.1:
     isstream "0.1.x"
     stack-trace "0.0.x"
 
+word-wrap@~1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+  integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+
 wordwrap@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
   integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
-
-wordwrap@~1.0.0:
+wordwrap@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
 
 worker-farm@^1.3.1, worker-farm@^1.5.2:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"
-  integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
   dependencies:
     errno "~0.1.7"
 
@@ -14343,6 +14072,15 @@ wrap-ansi@^2.0.0:
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
 
+wrap-ansi@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+  integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -14372,55 +14110,40 @@ ws@^1.0.1:
     options ">=0.0.5"
     ultron "1.0.x"
 
-x-is-function@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e"
-  integrity sha1-XSlNw9Joy90GJYDgxd93o5HR+h4=
-
 x-is-string@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
   integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
 
 xhr-mock@^2.4.1:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/xhr-mock/-/xhr-mock-2.4.1.tgz#cb502e3d50b8b2ec31bd61766ce516bfc1dd072f"
-  integrity sha1-y1AuPVC4suwxvWF2bOUWv8HdBy8=
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/xhr-mock/-/xhr-mock-2.5.1.tgz#c591498a8269cc1ce5fefac20d590357affd348b"
+  integrity sha512-UKOjItqjFgPUwQGPmRAzNBn8eTfIhcGjBVGvKYAWxUQPQsXNGD6KEckGTiHwyaAUp9C9igQlnN1Mp79KWCg7CQ==
   dependencies:
     global "^4.3.0"
     url "^0.11.0"
 
-xml-char-classes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"
-  integrity sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=
-
 xml-name-validator@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
   integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=
 
 xmldom@^0.1.22:
-  version "0.1.27"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-  integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
+  version "0.1.31"
+  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
+  integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
 
-"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
-  integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
-
-xtend@^4.0.1:
+"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
 
 xxhashjs@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.1.tgz#9bbe9be896142976dfa34c061b2d068c43d30de0"
-  integrity sha1-m76b6JYUKXbfo0wGGy0GjEPTDeA=
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"
+  integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==
   dependencies:
-    cuint latest
+    cuint "^0.2.2"
 
 y18n@^3.2.1:
   version "3.2.1"
@@ -14442,6 +14165,14 @@ yaml@^1.7.2:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
   integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
 
+yargs-parser@^18.1.2:
+  version "18.1.3"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+  integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^4.2.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
@@ -14456,32 +14187,7 @@ yargs-parser@^7.0.0:
   dependencies:
     camelcase "^4.1.0"
 
-yargs-parser@^8.0.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
-  integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==
-  dependencies:
-    camelcase "^4.1.0"
-
-yargs@^10.0.3:
-  version "10.0.3"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae"
-  integrity sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==
-  dependencies:
-    cliui "^3.2.0"
-    decamelize "^1.1.1"
-    find-up "^2.1.0"
-    get-caller-file "^1.0.1"
-    os-locale "^2.0.0"
-    require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
-    set-blocking "^2.0.0"
-    string-width "^2.0.0"
-    which-module "^2.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^8.0.0"
-
-yargs@^6.0.1, yargs@^6.3.0:
+yargs@6.6.0, yargs@^6.0.1, yargs@^6.3.0:
   version "6.6.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
   integrity sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=
@@ -14500,6 +14206,23 @@ yargs@^6.0.1, yargs@^6.3.0:
     y18n "^3.2.1"
     yargs-parser "^4.2.0"
 
+yargs@^15.3.1:
+  version "15.4.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+  integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
+  dependencies:
+    cliui "^6.0.0"
+    decamelize "^1.2.0"
+    find-up "^4.1.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^4.2.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^18.1.2"
+
 yargs@^8.0.2:
   version "8.0.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
@@ -14519,13 +14242,6 @@ yargs@^8.0.2:
     y18n "^3.2.1"
     yargs-parser "^7.0.0"
 
-yargs@~1.2.6:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.2.6.tgz#9c7b4a82fd5d595b2bf17ab6dcc43135432fe34b"
-  integrity sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=
-  dependencies:
-    minimist "^0.1.0"
-
 yargs@~3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"